/*-------------------------------------------------------------------------
 *
 * trigger.c
 *	  PostgreSQL 触发器支持代码。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *	  src/backend/commands/trigger.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/genam.h"
#include "access/htup_details.h"
#include "access/relation.h"
#include "access/sysattr.h"
#include "access/table.h"
#include "access/tableam.h"
#include "access/xact.h"
#include "catalog/catalog.h"
#include "catalog/dependency.h"
#include "catalog/index.h"
#include "catalog/indexing.h"
#include "catalog/objectaccess.h"
#include "catalog/partition.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
#include "commands/defrem.h"
#include "commands/trigger.h"
#include "executor/executor.h"
#include "executor/execPartition.h"
#include "miscadmin.h"
#include "nodes/bitmapset.h"
#include "nodes/makefuncs.h"
#include "optimizer/optimizer.h"
#include "parser/parse_clause.h"
#include "parser/parse_collate.h"
#include "parser/parse_func.h"
#include "parser/parse_relation.h"
#include "parser/parsetree.h"
#include "partitioning/partdesc.h"
#include "pgstat.h"
#include "rewrite/rewriteManip.h"
#include "storage/bufmgr.h"
#include "storage/lmgr.h"
#include "tcop/utility.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/bytea.h"
#include "utils/fmgroids.h"
#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/rel.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"
#include "utils/tuplestore.h"


/* GUC 变量 */
int			SessionReplicationRole = SESSION_REPLICATION_ROLE_ORIGIN;

/* 我们在触发器执行中深度多少级？ */
static int	MyTriggerDepth = 0;

/* 本地函数原型 */
static void fc_renametrig_internal(Relation fc_tgrel, Relation fc_targetrel,
								HeapTuple fc_trigtup, const char *fc_newname,
								const char *fc_expected_name);
static void fc_renametrig_partition(Relation fc_tgrel, Oid fc_partitionId,
								 Oid fc_parentTriggerOid, const char *fc_newname,
								 const char *fc_expected_name);
static void fc_SetTriggerFlags(TriggerDesc *fc_trigdesc, Trigger *fc_trigger);
static bool fc_GetTupleForTrigger(EState *fc_estate,
							   EPQState *fc_epqstate,
							   ResultRelInfo *fc_relinfo,
							   ItemPointer fc_tid,
							   LockTupleMode fc_lockmode,
							   TupleTableSlot *fc_oldslot,
							   TupleTableSlot **fc_epqslot,
							   TM_Result *fc_tmresultp,
							   TM_FailureData *fc_tmfdp);
static bool fc_TriggerEnabled(EState *fc_estate, ResultRelInfo *fc_relinfo,
						   Trigger *fc_trigger, TriggerEvent fc_event,
						   Bitmapset *fc_modifiedCols,
						   TupleTableSlot *fc_oldslot, TupleTableSlot *fc_newslot);
static HeapTuple fc_ExecCallTriggerFunc(TriggerData *fc_trigdata,
									 int fc_tgindx,
									 FmgrInfo *fc_finfo,
									 Instrumentation *fc_instr,
									 MemoryContext fc_per_tuple_context);
static void fc_AfterTriggerSaveEvent(EState *fc_estate, ResultRelInfo *fc_relinfo,
								  ResultRelInfo *fc_src_partinfo,
								  ResultRelInfo *fc_dst_partinfo,
								  int fc_event, bool fc_row_trigger,
								  TupleTableSlot *fc_oldtup, TupleTableSlot *fc_newtup,
								  List *fc_recheckIndexes, Bitmapset *fc_modifiedCols,
								  TransitionCaptureState *fc_transition_capture,
								  bool fc_is_crosspart_update);
static void fc_AfterTriggerEnlargeQueryState(void);
static bool fc_before_stmt_triggers_fired(Oid fc_relid, CmdType fc_cmdType);


/*
 * 创建一个触发器。返回创建的触发器的地址。
 *
 * queryString 是 CREATE TRIGGER 命令的源文本。
 * 如果指定了 whenClause，则必须提供此项，否则可以为 NULL。
 *
 * relOid，如果非零，则是触发器应该创建的关系。
 * 如果为零，则将查找语句中提供的名称。
 *
 * refRelOid，如果非零，则是约束触发器所引用的关系。
 * 如果为零，则将根据需要查找语句中提供的约束关系名称。
 *
 * constraintOid，如果非零，表示该触发器是内部创建以实现该约束。
 * 将创建一个合适的 pg_depend 条目以将触发器链接到该约束。
 * 在执行用户输入的 CREATE TRIGGER 命令时，constraintOid 为零。
 * （对于 CREATE CONSTRAINT TRIGGER，我们在内部构建一个 pg_constraint 条目。）
 *
 * indexOid，如果非零，则是与约束关联的索引的 OID。
 * 我们对此不做任何处理，只是在 pg_trigger.tgconstrindid 中存储它；
 * 但在为分区表上的可延迟唯一约束创建触发器时，会查找其子项。
 * 请注意，在这种情况下我们不处理无效索引。
 *
 * funcoid，如果非零，则是要调用的函数的 OID。给定此项时，将忽略 stmt->funcname。
 *
 * parentTriggerOid，如果非零，则是生成此触发器的触发器；
 * 因此如果该触发器被删除，则此触发器也应被删除。
 * 当传递非零值时，有两种情况：1）当此函数递归创建分区上的触发器时，
 * 2）创建子外键触发器时；参见 CreateFKCheckTrigger() 和 createForeignKeyActionTriggers()。
 *
 * 如果传递了 whenClause，则它是已转换的 WHEN 表达式。
 * 在这种情况下，我们忽略可能出现在 stmt->whenClause 中的任何内容。
 *
 * 如果 isInternal 为真，则这是一个内部生成的触发器。
 * 此参数设置 pg_trigger 条目的 tgisinternal 字段，
 * 如果为真，会导致我们修改给定的触发器名称以确保唯一性。
 *
 * 当 isInternal 不为真时，我们需要在关系上具有 ACL_TRIGGER 权限，
 * 以及在触发器函数上具有 ACL_EXECUTE 权限。
 * 对于内部触发器，调用者必须执行任何必要的权限检查。
 *
 * 在对分区表调用时，此函数会递归创建所有分区上的触发器，
 * 除非 isInternal 为真，在这种情况下期望调用者自行执行递归。
 * in_partition 表示这种递归调用；外部调用者应传递“false”
 * （但请参见 CloneRowTriggersToPartition）。
 */
ObjectAddress CreateTrigger(CreateTrigStmt *fc_stmt, const char *fc_queryString,
			  Oid fc_relOid, Oid fc_refRelOid, Oid fc_constraintOid, Oid fc_indexOid,
			  Oid fc_funcoid, Oid fc_parentTriggerOid, Node *fc_whenClause,
			  bool fc_isInternal, bool fc_in_partition)
{
	return
		CreateTriggerFiringOn(fc_stmt, fc_queryString, fc_relOid, fc_refRelOid,
							  fc_constraintOid, fc_indexOid, fc_funcoid,
							  fc_parentTriggerOid, fc_whenClause, fc_isInternal,
							  fc_in_partition, TRIGGER_FIRES_ON_ORIGIN);
}

/*
 * 与上述类似；此外，还可以指定触发条件
 * （总是/来源/副本/禁用）。
 */
ObjectAddress CreateTriggerFiringOn(CreateTrigStmt *fc_stmt, const char *fc_queryString,
					  Oid fc_relOid, Oid fc_refRelOid, Oid fc_constraintOid,
					  Oid fc_indexOid, Oid fc_funcoid, Oid fc_parentTriggerOid,
					  Node *fc_whenClause, bool fc_isInternal, bool fc_in_partition,
					  char fc_trigger_fires_when)
{
	int16		fc_tgtype;
	int			fc_ncolumns;
	int16	   *fc_columns;
	int2vector *fc_tgattr;
	List	   *fc_whenRtable;
	char	   *fc_qual;
	Datum		fc_values[Natts_pg_trigger];
	bool		fc_nulls[Natts_pg_trigger];
	Relation	fc_rel;
	AclResult	fc_aclresult;
	Relation	fc_tgrel;
	Relation	fc_pgrel;
	HeapTuple	fc_tuple = NULL;
	Oid			fc_funcrettype;
	Oid			fc_trigoid = InvalidOid;
	char		fc_internaltrigname[NAMEDATALEN];
	char	   *fc_trigname;
	Oid			fc_constrrelid = InvalidOid;
	ObjectAddress fc_myself,
				fc_referenced;
	char	   *fc_oldtablename = NULL;
	char	   *fc_newtablename = NULL;
	bool		fc_partition_recurse;
	bool		fc_trigger_exists = false;
	Oid			fc_existing_constraint_oid = InvalidOid;
	bool		fc_existing_isInternal = false;
	bool		fc_existing_isClone = false;

	if (OidIsValid(fc_relOid))
		fc_rel = table_open(fc_relOid, ShareRowExclusiveLock);
	else
		fc_rel = table_openrv(fc_stmt->relation, ShareRowExclusiveLock);

	/*
	 * 触发器必须在表或视图上，并且有额外的
	 * 特定于关系类型的限制。
	 */
	if (fc_rel->rd_rel->relkind == RELKIND_RELATION)
	{
		/* 表不能有 INSTEAD OF 触发器 */
		if (fc_stmt->timing != TRIGGER_TYPE_BEFORE &&
			fc_stmt->timing != TRIGGER_TYPE_AFTER)
			ereport(ERROR,
					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
					 errmsg("\"%s\" is a table",
							RelationGetRelationName(fc_rel)),
					 errdetail("Tables cannot have INSTEAD OF triggers.")));
	}
	else if (fc_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
	{
		/* 分区表不能有 INSTEAD OF 触发器 */
		if (fc_stmt->timing != TRIGGER_TYPE_BEFORE &&
			fc_stmt->timing != TRIGGER_TYPE_AFTER)
			ereport(ERROR,
					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
					 errmsg("\"%s\" is a table",
							RelationGetRelationName(fc_rel)),
					 errdetail("Tables cannot have INSTEAD OF triggers.")));

		/*
		 * 每行触发器有进一步的限制
		 */
		if (fc_stmt->row)
		{
			/*
			 * 不允许使用过渡表。
			 *
			 * 请注意，我们还有关于分区中过渡表的另一个限制；
			 * 请在下面搜索 'has_superclass' 查找解释。检查这里只是为了防止
			 * 这种情况：如果我们允许它在这里，创建会在没有分区的
			 * 分区表中成功，但在创建第一个分区时会被
			 * 其他限制阻止，这是一种非常不友好的行为。
			 */
			if (fc_stmt->transitionRels != NIL)
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("\"%s\" is a partitioned table",
								RelationGetRelationName(fc_rel)),
						 errdetail("ROW triggers with transition tables are not supported on partitioned tables.")));
		}
	}
	else if (fc_rel->rd_rel->relkind == RELKIND_VIEW)
	{
		/*
		 * 视图可以有 INSTEAD OF 触发器（我们在下面检查的是
		 * 行级触发器），或语句级的 BEFORE/AFTER 触发器。
		 */
		if (fc_stmt->timing != TRIGGER_TYPE_INSTEAD && fc_stmt->row)
			ereport(ERROR,
					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
					 errmsg("\"%s\" is a view",
							RelationGetRelationName(fc_rel)),
					 errdetail("Views cannot have row-level BEFORE or AFTER triggers.")));
		/* 不允许在视图上使用 TRUNCATE 触发器 */
		if (TRIGGER_FOR_TRUNCATE(fc_stmt->events))
			ereport(ERROR,
					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
					 errmsg("\"%s\" is a view",
							RelationGetRelationName(fc_rel)),
					 errdetail("Views cannot have TRUNCATE triggers.")));
	}
	else if (fc_rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
	{
		if (fc_stmt->timing != TRIGGER_TYPE_BEFORE &&
			fc_stmt->timing != TRIGGER_TYPE_AFTER)
			ereport(ERROR,
					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
					 errmsg("\"%s\" is a foreign table",
							RelationGetRelationName(fc_rel)),
					 errdetail("Foreign tables cannot have INSTEAD OF triggers.")));

		if (TRIGGER_FOR_TRUNCATE(fc_stmt->events))
			ereport(ERROR,
					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
					 errmsg("\"%s\" is a foreign table",
							RelationGetRelationName(fc_rel)),
					 errdetail("Foreign tables cannot have TRUNCATE triggers.")));

		/*
		 * 我们不允许约束触发器，以保护外键上
		 * 触发器不能被延迟的假设。请参见下文与 AfterTriggers
		 * 数据结构的说明。
		 */
		if (fc_stmt->isconstraint)
			ereport(ERROR,
					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
					 errmsg("\"%s\" is a foreign table",
							RelationGetRelationName(fc_rel)),
					 errdetail("Foreign tables cannot have constraint triggers.")));
	}
	else
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("relation \"%s\" cannot have triggers",
						RelationGetRelationName(fc_rel)),
				 errdetail_relkind_not_supported(fc_rel->rd_rel->relkind)));

	if (!allowSystemTableMods && IsSystemRelation(fc_rel))
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("permission denied: \"%s\" is a system catalog",
						RelationGetRelationName(fc_rel))));

	if (fc_stmt->isconstraint)
	{
		/*
		 * 我们必须在目标关系上加锁，以防止
		 * 并发删除。目前尚不清楚 AccessShareLock 是否足够强大，
		 * 但我们确实需要至少那么多……否则，我们可能最终会创建一个
		 * pg_constraint 条目，引用一个不存在的表。
		 */
		if (OidIsValid(fc_refRelOid))
		{
			LockRelationOid(fc_refRelOid, AccessShareLock);
			fc_constrrelid = fc_refRelOid;
		}
		else if (fc_stmt->constrrel != NULL)
			fc_constrrelid = RangeVarGetRelid(fc_stmt->constrrel, AccessShareLock,
										   false);
	}

	/* 权限检查 */
	if (!fc_isInternal)
	{
		fc_aclresult = pg_class_aclcheck(RelationGetRelid(fc_rel), GetUserId(),
									  ACL_TRIGGER);
		if (fc_aclresult != ACLCHECK_OK)
			aclcheck_error(fc_aclresult, get_relkind_objtype(fc_rel->rd_rel->relkind),
						   RelationGetRelationName(fc_rel));

		if (OidIsValid(fc_constrrelid))
		{
			fc_aclresult = pg_class_aclcheck(fc_constrrelid, GetUserId(),
										  ACL_TRIGGER);
			if (fc_aclresult != ACLCHECK_OK)
				aclcheck_error(fc_aclresult, get_relkind_objtype(get_rel_relkind(fc_constrrelid)),
							   get_rel_name(fc_constrrelid));
		}
	}

	/*
	 * 当在分区表上调用以创建一个非内部的每行触发器时，
	 * 我们也为每个分区创建一个触发器。
	 *
	 * 为此，我们最好提前对所有分区加锁。
	 */
	fc_partition_recurse = !fc_isInternal && fc_stmt->row &&
		fc_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;
	if (fc_partition_recurse)
		list_free(find_all_inheritors(RelationGetRelid(fc_rel),
									  ShareRowExclusiveLock, NULL));

	/* 计算 tgtype */
	TRIGGER_CLEAR_TYPE(fc_tgtype);
	if (fc_stmt->row)
		TRIGGER_SETT_ROW(fc_tgtype);
	fc_tgtype |= fc_stmt->timing;
	fc_tgtype |= fc_stmt->events;

	/* 不允许行级 TRUNCATE 触发器 */
	if (TRIGGER_FOR_ROW(fc_tgtype) && TRIGGER_FOR_TRUNCATE(fc_tgtype))
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("TRUNCATE FOR EACH ROW triggers are not supported")));

	/* INSTEAD 触发器必须是行级的，不能有 WHEN 或列 */
	if (TRIGGER_FOR_INSTEAD(fc_tgtype))
	{
		if (!TRIGGER_FOR_ROW(fc_tgtype))
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("INSTEAD OF triggers must be FOR EACH ROW")));
		if (fc_stmt->whenClause)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("INSTEAD OF triggers cannot have WHEN conditions")));
		if (fc_stmt->columns != NIL)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("INSTEAD OF triggers cannot have column lists")));
	}

	/*
	 * 我们尚不支持命名行过渡变量，但解析器
	 * 识别语法，因此我们可以在这里给出更好的消息。
	 *
	 * 根据标准，REFERENCING TABLE 名称仅允许在 AFTER
	 * 触发器中使用。根据标准，REFERENCING ROW 名称在
	 * 每个语句中不允许使用。根据标准，OLD/NEW、ROW/TABLE
	 * 的每个组合仅允许使用一次。根据标准，创建仅用于
	 * INSERT 的触发器时不允许指定 OLD，创建仅用于
	 * DELETE 的触发器时不允许指定 NEW。
	 *
	 * 请注意，标准允许一个 AFTER ... FOR EACH ROW 触发器
	 * 同时引用行和表的过渡数据。
	 */
	if (fc_stmt->transitionRels != NIL)
	{
		List	   *fc_varList = fc_stmt->transitionRels;
		ListCell   *fc_lc;

		foreach(fc_lc, fc_varList)
		{
			TriggerTransition *fc_tt = lfirst_node(TriggerTransition, fc_lc);

			if (!(fc_tt->isTable))
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("ROW variable naming in the REFERENCING clause is not supported"),
						 errhint("Use OLD TABLE or NEW TABLE for naming transition tables.")));

			/*
			 * 由于上面的测试，我们在下面省略进一步的行相关测试。
			 * 如果我们之后允许命名 OLD 和 NEW ROW 变量，
			 * 将需要在下面进行调整。
			 */

			if (fc_rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
				ereport(ERROR,
						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
						 errmsg("\"%s\" is a foreign table",
								RelationGetRelationName(fc_rel)),
						 errdetail("Triggers on foreign tables cannot have transition tables.")));

			if (fc_rel->rd_rel->relkind == RELKIND_VIEW)
				ereport(ERROR,
						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
						 errmsg("\"%s\" is a view",
								RelationGetRelationName(fc_rel)),
						 errdetail("Triggers on views cannot have transition tables.")));

			/*
			 * 我们目前不允许在分区或继承子表上使用具有过渡表的行级触发器。这种触发器需要以某种方式查看转换为它们所附加表格式的元组，但并不清楚每个子表应该看到哪些元组的子集。另请参阅ATExecAttachPartition()和ATExecAddInherit()中的禁令。
			 */
			if (TRIGGER_FOR_ROW(fc_tgtype) && has_superclass(fc_rel->rd_id))
			{
				/* 使用适当的错误消息。 */
				if (fc_rel->rd_rel->relispartition)
					ereport(ERROR,
							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
							 errmsg("ROW triggers with transition tables are not supported on partitions")));
				else
					ereport(ERROR,
							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
							 errmsg("ROW triggers with transition tables are not supported on inheritance children")));
			}

			if (fc_stmt->timing != TRIGGER_TYPE_AFTER)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
						 errmsg("transition table name can only be specified for an AFTER trigger")));

			if (TRIGGER_FOR_TRUNCATE(fc_tgtype))
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("TRUNCATE triggers with transition tables are not supported")));

			/*
			 * 我们目前不允许具有过渡表的多事件触发器（"INSERT OR UPDATE"），因为如何处理可能同时触发INSERT和UPDATE触发器的INSERT ... ON CONFLICT语句并不明确。我们向INSERT触发器显示插入的元组，而向UPDATE触发器显示更新的元组，但尚不清楚INSERT OR UPDATE触发器应该看到什么。如果我们能在后续版本中决定出正确的语义，这一限制可能会被解除。
			 */
			if (((TRIGGER_FOR_INSERT(fc_tgtype) ? 1 : 0) +
				 (TRIGGER_FOR_UPDATE(fc_tgtype) ? 1 : 0) +
				 (TRIGGER_FOR_DELETE(fc_tgtype) ? 1 : 0)) != 1)
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("transition tables cannot be specified for triggers with more than one event")));

			/*
			 * 我们目前不允许具有过渡表的列特定触发器。根据规范，这似乎需要为每种列的组合累积单独的过渡表，这对一个相当边缘的功能来说工作量很大。
			 */
			if (fc_stmt->columns != NIL)
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("transition tables cannot be specified for triggers with column lists")));

			/*
			 * 我们不允许使用具有过渡表的约束触发器，以保护这种触发器不能被延迟的假设。请参阅下面的AfterTriggers数据结构中的说明。
			 *
			 * 目前，这通过语法得到强制，因此在这里仅需Assert。
			 */
			Assert(!fc_stmt->isconstraint);

			if (fc_tt->isNew)
			{
				if (!(TRIGGER_FOR_INSERT(fc_tgtype) ||
					  TRIGGER_FOR_UPDATE(fc_tgtype)))
					ereport(ERROR,
							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
							 errmsg("NEW TABLE can only be specified for an INSERT or UPDATE trigger")));

				if (fc_newtablename != NULL)
					ereport(ERROR,
							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
							 errmsg("NEW TABLE cannot be specified multiple times")));

				fc_newtablename = fc_tt->name;
			}
			else
			{
				if (!(TRIGGER_FOR_DELETE(fc_tgtype) ||
					  TRIGGER_FOR_UPDATE(fc_tgtype)))
					ereport(ERROR,
							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
							 errmsg("OLD TABLE can only be specified for a DELETE or UPDATE trigger")));

				if (fc_oldtablename != NULL)
					ereport(ERROR,
							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
							 errmsg("OLD TABLE cannot be specified multiple times")));

				fc_oldtablename = fc_tt->name;
			}
		}

		if (fc_newtablename != NULL && fc_oldtablename != NULL &&
			strcmp(fc_newtablename, fc_oldtablename) == 0)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
					 errmsg("OLD TABLE name and NEW TABLE name cannot be the same")));
	}

	/*
	 * 解析WHEN子句（如果有），且我们没有传递一个已经转换的子句。
	 *
	 * 请注意，作为副作用，我们在解析时填充whenRtable。如果得到了一个已经解析的子句，则不会发生这种情况，这正是我们想要的——在下面没有必要添加冗余依赖关系。
	 */
	if (!fc_whenClause && fc_stmt->whenClause)
	{
		ParseState *fc_pstate;
		ParseNamespaceItem *fc_nsitem;
		List	   *fc_varList;
		ListCell   *fc_lc;

		/* 设置一个要解析的pstate */
		fc_pstate = make_parsestate(NULL);
		fc_pstate->p_sourcetext = fc_queryString;

		/*
		 * 为OLD和NEW引用设置nsitems。
		 *
		 * 'OLD'的varno必须始终等于1，'NEW'等于2。
		 */
		fc_nsitem = addRangeTableEntryForRelation(fc_pstate, fc_rel,
											   AccessShareLock,
											   makeAlias("old", NIL),
											   false, false);
		addNSItemToQuery(fc_pstate, fc_nsitem, false, true, true);
		fc_nsitem = addRangeTableEntryForRelation(fc_pstate, fc_rel,
											   AccessShareLock,
											   makeAlias("new", NIL),
											   false, false);
		addNSItemToQuery(fc_pstate, fc_nsitem, false, true, true);

		/* 转换表达式。复制以确保不修改原始内容 */
		fc_whenClause = transformWhereClause(fc_pstate,
										  copyObject(fc_stmt->whenClause),
										  EXPR_KIND_TRIGGER_WHEN,
										  "WHEN");
		/* 我们还必须修复它的排序规则 */
		assign_expr_collations(fc_pstate, fc_whenClause);

		/*
		 * 检查不允许对OLD/NEW的引用。
		 *
		 * 注意：pull_var_clause在这里是可以的，因为我们不允许在WHEN子句中使用子选择；它将无法检查子选择的内容。
		 */
		fc_varList = pull_var_clause(fc_whenClause, 0);
		foreach(fc_lc, fc_varList)
		{
			Var		   *fc_var = (Var *) lfirst(fc_lc);

			switch (fc_var->varno)
			{
				case PRS2_OLD_VARNO:
					if (!TRIGGER_FOR_ROW(fc_tgtype))
						ereport(ERROR,
								(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
								 errmsg("statement trigger's WHEN condition cannot reference column values"),
								 parser_errposition(fc_pstate, fc_var->location)));
					if (TRIGGER_FOR_INSERT(fc_tgtype))
						ereport(ERROR,
								(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
								 errmsg("INSERT trigger's WHEN condition cannot reference OLD values"),
								 parser_errposition(fc_pstate, fc_var->location)));
					/* 系统列在这里是可以的 */
					break;
				case PRS2_NEW_VARNO:
					if (!TRIGGER_FOR_ROW(fc_tgtype))
						ereport(ERROR,
								(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
								 errmsg("statement trigger's WHEN condition cannot reference column values"),
								 parser_errposition(fc_pstate, fc_var->location)));
					if (TRIGGER_FOR_DELETE(fc_tgtype))
						ereport(ERROR,
								(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
								 errmsg("DELETE trigger's WHEN condition cannot reference NEW values"),
								 parser_errposition(fc_pstate, fc_var->location)));
					if (fc_var->varattno < 0 && TRIGGER_FOR_BEFORE(fc_tgtype))
						ereport(ERROR,
								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
								 errmsg("BEFORE trigger's WHEN condition cannot reference NEW system columns"),
								 parser_errposition(fc_pstate, fc_var->location)));
					if (TRIGGER_FOR_BEFORE(fc_tgtype) &&
						fc_var->varattno == 0 &&
						RelationGetDescr(fc_rel)->constr &&
						RelationGetDescr(fc_rel)->constr->has_generated_stored)
						ereport(ERROR,
								(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
								 errmsg("BEFORE trigger's WHEN condition cannot reference NEW generated columns"),
								 errdetail("A whole-row reference is used and the table contains generated columns."),
								 parser_errposition(fc_pstate, fc_var->location)));
					if (TRIGGER_FOR_BEFORE(fc_tgtype) &&
						fc_var->varattno > 0 &&
						TupleDescAttr(RelationGetDescr(fc_rel), fc_var->varattno - 1)->attgenerated)
						ereport(ERROR,
								(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
								 errmsg("BEFORE trigger's WHEN condition cannot reference NEW generated columns"),
								 errdetail("Column \"%s\" is a generated column.",
										   NameStr(TupleDescAttr(RelationGetDescr(fc_rel), fc_var->varattno - 1)->attname)),
								 parser_errposition(fc_pstate, fc_var->location)));
					break;
				default:
					/* 没有add_missing_from的话，不会发生，因此只需elog */
					elog(ERROR, "trigger WHEN condition cannot contain references to other relations");
					break;
			}
		}

		/* 我们需要rtable以记录对Expr的依赖关系 */
		fc_whenRtable = fc_pstate->p_rtable;

		fc_qual = nodeToString(fc_whenClause);

		free_parsestate(fc_pstate);
	}
	else if (!fc_whenClause)
	{
		fc_whenClause = NULL;
		fc_whenRtable = NIL;
		fc_qual = NULL;
	}
	else
	{
		fc_qual = nodeToString(fc_whenClause);
		fc_whenRtable = NIL;
	}

	/*
	 * 查找并验证触发函数。
	 */
	if (!OidIsValid(fc_funcoid))
		fc_funcoid = LookupFuncName(fc_stmt->funcname, 0, NULL, false);
	if (!fc_isInternal)
	{
		fc_aclresult = pg_proc_aclcheck(fc_funcoid, GetUserId(), ACL_EXECUTE);
		if (fc_aclresult != ACLCHECK_OK)
			aclcheck_error(fc_aclresult, OBJECT_FUNCTION,
						   NameListToString(fc_stmt->funcname));
	}
	fc_funcrettype = get_func_rettype(fc_funcoid);
	if (fc_funcrettype != TRIGGEROID)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
				 errmsg("function %s must return type %s",
						NameListToString(fc_stmt->funcname), "trigger")));

	/*
	 * 扫描pg_trigger查看是否已经有同名的触发器。跳过内部生成的触发器，因为我们将在下面修改名称以确保唯一。
	 *
	 * 注意，这样做是可行的，仅因为我们在关系上持有ShareRowExclusiveLock，因此触发器集合不会在我们下面更改。
	 */
	fc_tgrel = table_open(TriggerRelationId, RowExclusiveLock);
	if (!fc_isInternal)
	{
		ScanKeyData fc_skeys[2];
		SysScanDesc fc_tgscan;

		ScanKeyInit(&fc_skeys[0],
					Anum_pg_trigger_tgrelid,
					BTEqualStrategyNumber, F_OIDEQ,
					ObjectIdGetDatum(RelationGetRelid(fc_rel)));

		ScanKeyInit(&fc_skeys[1],
					Anum_pg_trigger_tgname,
					BTEqualStrategyNumber, F_NAMEEQ,
					CStringGetDatum(fc_stmt->trigname));

		fc_tgscan = systable_beginscan(fc_tgrel, TriggerRelidNameIndexId, true,
									NULL, 2, fc_skeys);

		/* 至多应有一个匹配的元组 */
		if (HeapTupleIsValid(fc_tuple = systable_getnext(fc_tgscan)))
		{
			Form_pg_trigger fc_oldtrigger = (Form_pg_trigger) GETSTRUCT(fc_tuple);

			fc_trigoid = fc_oldtrigger->oid;
			fc_existing_constraint_oid = fc_oldtrigger->tgconstraint;
			fc_existing_isInternal = fc_oldtrigger->tgisinternal;
			fc_existing_isClone = OidIsValid(fc_oldtrigger->tgparentid);
			fc_trigger_exists = true;
			/* 复制元组以便在CatalogTupleUpdate()中使用 */
			fc_tuple = heap_copytuple(fc_tuple);
		}
		systable_endscan(fc_tgscan);
	}

	if (!fc_trigger_exists)
	{
		/* 为新触发器生成OID。 */
		fc_trigoid = GetNewOidWithIndex(fc_tgrel, TriggerOidIndexId,
									 Anum_pg_trigger_oid);
	}
	else
	{
		/*
		 * 如果指定了OR REPLACE，我们将替换旧的触发器；
		 * 否则，抱怨名称重复。
		 */
		if (!fc_stmt->replace)
			ereport(ERROR,
					(errcode(ERRCODE_DUPLICATE_OBJECT),
					 errmsg("trigger \"%s\" for relation \"%s\" already exists",
							fc_stmt->trigname, RelationGetRelationName(fc_rel))));

		/*
		 * 内部触发器或子触发器(isClone)不能被用户定义的触发器替换。
		 * 然而，在in_partition时跳过此测试，因为那时我们正在从一个分区表递归
		 * 而检查是在父级进行的。
		 */
		if ((fc_existing_isInternal || fc_existing_isClone) &&
			!fc_isInternal && !fc_in_partition)
			ereport(ERROR,
					(errcode(ERRCODE_DUPLICATE_OBJECT),
					 errmsg("trigger \"%s\" for relation \"%s\" is an internal or a child trigger",
							fc_stmt->trigname, RelationGetRelationName(fc_rel))));

		/*
		 * 不允许用约束触发器替换；gram.y
		 * 应该已经执行了这一点。
		 */
		Assert(!fc_stmt->isconstraint);

		/*
		 * 也不允许替换现有的约束触发器。
		 * （这些限制的原因部分是因为在这种情况下处理待处理的触发器事件似乎很困难，
		 * 部分原因是该命令可能意味着改变约束的属性，这似乎不太好。）
		 */
		if (OidIsValid(fc_existing_constraint_oid))
			ereport(ERROR,
					(errcode(ERRCODE_DUPLICATE_OBJECT),
					 errmsg("trigger \"%s\" for relation \"%s\" is a constraint trigger",
							fc_stmt->trigname, RelationGetRelationName(fc_rel))));
	}

	/*
	 * 如果这是用户输入的CREATE CONSTRAINT TRIGGER命令，
	 * 则创建相应的pg_constraint条目。
	 */
	if (fc_stmt->isconstraint && !OidIsValid(fc_constraintOid))
	{
		/* 内部调用者应该已经创建了自己的约束 */
		Assert(!fc_isInternal);
		fc_constraintOid = CreateConstraintEntry(fc_stmt->trigname,
											  RelationGetNamespace(fc_rel),
											  CONSTRAINT_TRIGGER,
											  fc_stmt->deferrable,
											  fc_stmt->initdeferred,
											  true,
											  InvalidOid,	/* 没有父级 */
											  RelationGetRelid(fc_rel),
											  NULL, /* 没有conkey */
											  0,
											  0,
											  InvalidOid,	/* 没有域 */
											  InvalidOid,	/* 没有索引 */
											  InvalidOid,	/* 没有外键 */
											  NULL,
											  NULL,
											  NULL,
											  NULL,
											  0,
											  ' ',
											  ' ',
											  NULL,
											  0,
											  ' ',
											  NULL, /* 没有排除 */
											  NULL, /* 没有检查约束 */
											  NULL,
											  true, /* islocal */
											  0,	/* inhcount */
											  true, /* noinherit */
											  fc_isInternal);	/* is_internal */
	}

	/*
	 * 如果触发器是内部生成的，将提供的触发器名称修改为
	 * 通过附加触发器OID确保唯一性。 （调用者通常
	 * 在这些情况下会提供一个简单的常量触发器名称。）
	 */
	if (fc_isInternal)
	{
		snprintf(fc_internaltrigname, sizeof(fc_internaltrigname),
				 "%s_%u", fc_stmt->trigname, fc_trigoid);
		fc_trigname = fc_internaltrigname;
	}
	else
	{
		/* 用户定义的触发器；按原样使用指定的触发器名称 */
		fc_trigname = fc_stmt->trigname;
	}

	/*
	 * 构建新的pg_trigger元组。
	 */
	memset(fc_nulls, false, sizeof(fc_nulls));

	fc_values[Anum_pg_trigger_oid - 1] = ObjectIdGetDatum(fc_trigoid);
	fc_values[Anum_pg_trigger_tgrelid - 1] = ObjectIdGetDatum(RelationGetRelid(fc_rel));
	fc_values[Anum_pg_trigger_tgparentid - 1] = ObjectIdGetDatum(fc_parentTriggerOid);
	fc_values[Anum_pg_trigger_tgname - 1] = DirectFunctionCall1(namein,
															 CStringGetDatum(fc_trigname));
	fc_values[Anum_pg_trigger_tgfoid - 1] = ObjectIdGetDatum(fc_funcoid);
	fc_values[Anum_pg_trigger_tgtype - 1] = Int16GetDatum(fc_tgtype);
	fc_values[Anum_pg_trigger_tgenabled - 1] = fc_trigger_fires_when;
	fc_values[Anum_pg_trigger_tgisinternal - 1] = BoolGetDatum(fc_isInternal);
	fc_values[Anum_pg_trigger_tgconstrrelid - 1] = ObjectIdGetDatum(fc_constrrelid);
	fc_values[Anum_pg_trigger_tgconstrindid - 1] = ObjectIdGetDatum(fc_indexOid);
	fc_values[Anum_pg_trigger_tgconstraint - 1] = ObjectIdGetDatum(fc_constraintOid);
	fc_values[Anum_pg_trigger_tgdeferrable - 1] = BoolGetDatum(fc_stmt->deferrable);
	fc_values[Anum_pg_trigger_tginitdeferred - 1] = BoolGetDatum(fc_stmt->initdeferred);

	if (fc_stmt->args)
	{
		ListCell   *fc_le;
		char	   *fc_args;
		int16		fc_nargs = list_length(fc_stmt->args);
		int			fc_len = 0;

		foreach(fc_le, fc_stmt->args)
		{
			char	   *fc_ar = strVal(lfirst(fc_le));

			fc_len += strlen(fc_ar) + 4;
			for (; *fc_ar; fc_ar++)
			{
				if (*fc_ar == '\\')
					fc_len++;
			}
		}
		fc_args = (char *) palloc(fc_len + 1);
		fc_args[0] = '\0';
		foreach(fc_le, fc_stmt->args)
		{
			char	   *fc_s = strVal(lfirst(fc_le));
			char	   *fc_d = fc_args + strlen(fc_args);

			while (*fc_s)
			{
				if (*fc_s == '\\')
					*fc_d++ = '\\';
				*fc_d++ = *fc_s++;
			}
			strcpy(fc_d, "\\000");
		}
		fc_values[Anum_pg_trigger_tgnargs - 1] = Int16GetDatum(fc_nargs);
		fc_values[Anum_pg_trigger_tgargs - 1] = DirectFunctionCall1(byteain,
																 CStringGetDatum(fc_args));
	}
	else
	{
		fc_values[Anum_pg_trigger_tgnargs - 1] = Int16GetDatum(0);
		fc_values[Anum_pg_trigger_tgargs - 1] = DirectFunctionCall1(byteain,
																 CStringGetDatum(""));
	}

	/* 如果是列特定触发器，构建列号数组 */
	fc_ncolumns = list_length(fc_stmt->columns);
	if (fc_ncolumns == 0)
		fc_columns = NULL;
	else
	{
		ListCell   *fc_cell;
		int			fc_i = 0;

		fc_columns = (int16 *) palloc(fc_ncolumns * sizeof(int16));
		foreach(fc_cell, fc_stmt->columns)
		{
			char	   *fc_name = strVal(lfirst(fc_cell));
			int16		fc_attnum;
			int			fc_j;

			/* 查找列名。 不允许系统列 */
			fc_attnum = attnameAttNum(fc_rel, fc_name, false);
			if (fc_attnum == InvalidAttrNumber)
				ereport(ERROR,
						(errcode(ERRCODE_UNDEFINED_COLUMN),
						 errmsg("column \"%s\" of relation \"%s\" does not exist",
								fc_name, RelationGetRelationName(fc_rel))));

			/* 检查重复 */
			for (fc_j = fc_i - 1; fc_j >= 0; fc_j--)
			{
				if (fc_columns[fc_j] == fc_attnum)
					ereport(ERROR,
							(errcode(ERRCODE_DUPLICATE_COLUMN),
							 errmsg("column \"%s\" specified more than once",
									fc_name)));
			}

			fc_columns[fc_i++] = fc_attnum;
		}
	}
	fc_tgattr = buildint2vector(fc_columns, fc_ncolumns);
	fc_values[Anum_pg_trigger_tgattr - 1] = PointerGetDatum(fc_tgattr);

	/* 如果触发器具有WHEN子句，则设置tgqual */
	if (fc_qual)
		fc_values[Anum_pg_trigger_tgqual - 1] = CStringGetTextDatum(fc_qual);
	else
		fc_nulls[Anum_pg_trigger_tgqual - 1] = true;

	if (fc_oldtablename)
		fc_values[Anum_pg_trigger_tgoldtable - 1] = DirectFunctionCall1(namein,
																	 CStringGetDatum(fc_oldtablename));
	else
		fc_nulls[Anum_pg_trigger_tgoldtable - 1] = true;
	if (fc_newtablename)
		fc_values[Anum_pg_trigger_tgnewtable - 1] = DirectFunctionCall1(namein,
																	 CStringGetDatum(fc_newtablename));
	else
		fc_nulls[Anum_pg_trigger_tgnewtable - 1] = true;

	/*
	 * 在pg_trigger中插入或替换元组。
	 */
	if (!fc_trigger_exists)
	{
		fc_tuple = heap_form_tuple(fc_tgrel->rd_att, fc_values, fc_nulls);
		CatalogTupleInsert(fc_tgrel, fc_tuple);
	}
	else
	{
		HeapTuple	fc_newtup;

		fc_newtup = heap_form_tuple(fc_tgrel->rd_att, fc_values, fc_nulls);
		CatalogTupleUpdate(fc_tgrel, &fc_tuple->t_self, fc_newtup);
		heap_freetuple(fc_newtup);
	}

	heap_freetuple(fc_tuple);		/* 释放原始元组或新元组中的任一个 */
	table_close(fc_tgrel, RowExclusiveLock);

	pfree(DatumGetPointer(fc_values[Anum_pg_trigger_tgname - 1]));
	pfree(DatumGetPointer(fc_values[Anum_pg_trigger_tgargs - 1]));
	pfree(DatumGetPointer(fc_values[Anum_pg_trigger_tgattr - 1]));
	if (fc_oldtablename)
		pfree(DatumGetPointer(fc_values[Anum_pg_trigger_tgoldtable - 1]));
	if (fc_newtablename)
		pfree(DatumGetPointer(fc_values[Anum_pg_trigger_tgnewtable - 1]));

	/*
	 * 更新关系的pg_class条目；如有必要；如果没有，发送SI
	 * 消息以使其他后端（以及本后端）重建relcache条目。
	 */
	fc_pgrel = table_open(RelationRelationId, RowExclusiveLock);
	fc_tuple = SearchSysCacheCopy1(RELOID,
								ObjectIdGetDatum(RelationGetRelid(fc_rel)));
	if (!HeapTupleIsValid(fc_tuple))
		elog(ERROR, "cache lookup failed for relation %u",
			 RelationGetRelid(fc_rel));
	if (!((Form_pg_class) GETSTRUCT(fc_tuple))->relhastriggers)
	{
		((Form_pg_class) GETSTRUCT(fc_tuple))->relhastriggers = true;

		CatalogTupleUpdate(fc_pgrel, &fc_tuple->t_self, fc_tuple);

		CommandCounterIncrement();
	}
	else
		CacheInvalidateRelcacheByTuple(fc_tuple);

	heap_freetuple(fc_tuple);
	table_close(fc_pgrel, RowExclusiveLock);

	/*
	 * 如果我们正在替换触发器，请在记录新依赖项之前刷新所有旧依赖项。
	 */
	if (fc_trigger_exists)
		deleteDependencyRecordsFor(TriggerRelationId, fc_trigoid, true);

	/*
	 * 记录触发器的依赖项。始终对函数放置一个正常依赖项。
	 */
	fc_myself.classId = TriggerRelationId;
	fc_myself.objectId = fc_trigoid;
	fc_myself.objectSubId = 0;

	fc_referenced.classId = ProcedureRelationId;
	fc_referenced.objectId = fc_funcoid;
	fc_referenced.objectSubId = 0;
	recordDependencyOn(&fc_myself, &fc_referenced, DEPENDENCY_NORMAL);

	if (fc_isInternal && OidIsValid(fc_constraintOid))
	{
		/*
		 * 所以它是一个约束的内部生成触发器，因此使其成为
		 * 约束的内部依赖项。我们可以跳过依赖于
		 * 关系，因为将通过约束有一个间接依赖项。
		 */
		fc_referenced.classId = ConstraintRelationId;
		fc_referenced.objectId = fc_constraintOid;
		fc_referenced.objectSubId = 0;
		recordDependencyOn(&fc_myself, &fc_referenced, DEPENDENCY_INTERNAL);
	}
	else
	{
		/*
		 * 用户CREATE TRIGGER，所以放置依赖项。我们使触发器在
		 * 其关系被删除或外键关系被删除时自动删除。
		 * （自动删除与我们7.3之前的行为兼容。）
		 */
		fc_referenced.classId = RelationRelationId;
		fc_referenced.objectId = RelationGetRelid(fc_rel);
		fc_referenced.objectSubId = 0;
		recordDependencyOn(&fc_myself, &fc_referenced, DEPENDENCY_AUTO);

		if (OidIsValid(fc_constrrelid))
		{
			fc_referenced.classId = RelationRelationId;
			fc_referenced.objectId = fc_constrrelid;
			fc_referenced.objectSubId = 0;
			recordDependencyOn(&fc_myself, &fc_referenced, DEPENDENCY_AUTO);
		}
		/* 在这种情况下不可能有索引依赖项 */
		Assert(!OidIsValid(fc_indexOid));

		/*
		 * 如果这是用户指定的约束触发器，则使约束
		 * 在内部依赖于触发器，而不是反过来。
		 */
		if (OidIsValid(fc_constraintOid))
		{
			fc_referenced.classId = ConstraintRelationId;
			fc_referenced.objectId = fc_constraintOid;
			fc_referenced.objectSubId = 0;
			recordDependencyOn(&fc_referenced, &fc_myself, DEPENDENCY_INTERNAL);
		}

		/*
		 * 如果这是一个分区触发器，创建分区依赖关系。
		 */
		if (OidIsValid(fc_parentTriggerOid))
		{
			ObjectAddressSet(fc_referenced, TriggerRelationId, fc_parentTriggerOid);
			recordDependencyOn(&fc_myself, &fc_referenced, DEPENDENCY_PARTITION_PRI);
			ObjectAddressSet(fc_referenced, RelationRelationId, RelationGetRelid(fc_rel));
			recordDependencyOn(&fc_myself, &fc_referenced, DEPENDENCY_PARTITION_SEC);
		}
	}

	/* 如果是列特定触发器，则在列上添加正常依赖关系 */
	if (fc_columns != NULL)
	{
		int			fc_i;

		fc_referenced.classId = RelationRelationId;
		fc_referenced.objectId = RelationGetRelid(fc_rel);
		for (fc_i = 0; fc_i < fc_ncolumns; fc_i++)
		{
			fc_referenced.objectSubId = fc_columns[fc_i];
			recordDependencyOn(&fc_myself, &fc_referenced, DEPENDENCY_NORMAL);
		}
	}

	/*
	 * 如果有WHEN子句，则在表达式中提到的对象上添加依赖关系
	 * （例如，函数，以及使用的任何列）。
	 */
	if (fc_whenRtable != NIL)
		recordDependencyOnExpr(&fc_myself, fc_whenClause, fc_whenRtable,
							   DEPENDENCY_NORMAL);

	/* 新触发器的创建后钩子 */
	InvokeObjectPostCreateHookArg(TriggerRelationId, fc_trigoid, 0,
								  fc_isInternal);

	/*
	 * 最后，如果需要，在子关系上创建触发器。
	 */
	if (fc_partition_recurse)
	{
		PartitionDesc fc_partdesc = RelationGetPartitionDesc(fc_rel, true);
		List	   *fc_idxs = NIL;
		List	   *fc_childTbls = NIL;
		ListCell   *fc_l;
		int			fc_i;
		MemoryContext fc_oldcxt,
					fc_perChildCxt;

		fc_perChildCxt = AllocSetContextCreate(CurrentMemoryContext,
											"part trig clone",
											ALLOCSET_SMALL_SIZES);

		/*
		 * 当与索引相关联的触发器被创建时，我们需要
		 * 在每个子分区中将触发器与相应的索引关联。
		 */
		if (OidIsValid(fc_indexOid))
		{
			ListCell   *fc_l;
			List	   *fc_idxs = NIL;

			fc_idxs = find_inheritance_children(fc_indexOid, ShareRowExclusiveLock);
			foreach(fc_l, fc_idxs)
				fc_childTbls = lappend_oid(fc_childTbls,
										IndexGetRelation(lfirst_oid(fc_l),
														 false));
		}

		fc_oldcxt = MemoryContextSwitchTo(fc_perChildCxt);

		/* 迭代以在每个现有分区上创建触发器 */
		for (fc_i = 0; fc_i < fc_partdesc->nparts; fc_i++)
		{
			Oid			fc_indexOnChild = InvalidOid;
			ListCell   *fc_l2;
			CreateTrigStmt *fc_childStmt;
			Relation	fc_childTbl;
			Node	   *fc_qual;

			fc_childTbl = table_open(fc_partdesc->oids[fc_i], ShareRowExclusiveLock);

			/* 找到在此分区上的子索引 */
			if (OidIsValid(fc_indexOid))
			{
				forboth(fc_l, fc_idxs, fc_l2, fc_childTbls)
				{
					if (lfirst_oid(fc_l2) == fc_partdesc->oids[fc_i])
					{
						fc_indexOnChild = lfirst_oid(fc_l);
						break;
					}
				}
				if (!OidIsValid(fc_indexOnChild))
					elog(ERROR, "failed to find index matching index \"%s\" in partition \"%s\"",
						 get_rel_name(fc_indexOid),
						 get_rel_name(fc_partdesc->oids[fc_i]));
			}

			/*
			 * 通过复制原始解析节点来初始化我们制造的解析节点，
			 * 然后重置我们单独传递的字段。
			 */
			fc_childStmt = (CreateTrigStmt *) copyObject(fc_stmt);
			fc_childStmt->funcname = NIL;
			fc_childStmt->whenClause = NULL;

			/* 如果有WHEN子句，创建其修改的副本 */
			fc_qual = copyObject(fc_whenClause);
			fc_qual = (Node *)
				map_partition_varattnos((List *) fc_qual, PRS2_OLD_VARNO,
										fc_childTbl, fc_rel);
			fc_qual = (Node *)
				map_partition_varattnos((List *) fc_qual, PRS2_NEW_VARNO,
										fc_childTbl, fc_rel);

			CreateTriggerFiringOn(fc_childStmt, fc_queryString,
								  fc_partdesc->oids[fc_i], fc_refRelOid,
								  InvalidOid, fc_indexOnChild,
								  fc_funcoid, fc_trigoid, fc_qual,
								  fc_isInternal, true, fc_trigger_fires_when);

			table_close(fc_childTbl, NoLock);

			MemoryContextReset(fc_perChildCxt);
		}

		MemoryContextSwitchTo(fc_oldcxt);
		MemoryContextDelete(fc_perChildCxt);
		list_free(fc_idxs);
		list_free(fc_childTbls);
	}

	/* 在事务结束前保持对目标关系的锁定 */
	table_close(fc_rel, NoLock);

	return fc_myself;
}

/*
 * TriggerSetParentTrigger
 *		将分区的触发器设置为其父触发器的子触发器，
 *		或如果parentTrigId是InvalidOid则移除链接。
 *
 * 这将更新约束的pg_trigger行以显示为继承，并
 * 添加PARTITION依赖关系以防止触发器被单独删除。
 * 或者，反向操作。
 */
void TriggerSetParentTrigger(Relation fc_trigRel,
						Oid fc_childTrigId,
						Oid fc_parentTrigId,
						Oid fc_childTableId)
{
	SysScanDesc fc_tgscan;
	ScanKeyData fc_skey[1];
	Form_pg_trigger fc_trigForm;
	HeapTuple	fc_tuple,
				fc_newtup;
	ObjectAddress fc_depender;
	ObjectAddress fc_referenced;

	/*
	 * 找到要删除的触发器。
	 */
	ScanKeyInit(&fc_skey[0],
				Anum_pg_trigger_oid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_childTrigId));

	fc_tgscan = systable_beginscan(fc_trigRel, TriggerOidIndexId, true,
								NULL, 1, fc_skey);

	fc_tuple = systable_getnext(fc_tgscan);
	if (!HeapTupleIsValid(fc_tuple))
		elog(ERROR, "could not find tuple for trigger %u", fc_childTrigId);
	fc_newtup = heap_copytuple(fc_tuple);
	fc_trigForm = (Form_pg_trigger) GETSTRUCT(fc_newtup);
	if (OidIsValid(fc_parentTrigId))
	{
		/* 不允许为已拥有父触发器的约束设置父触发器 */
		if (OidIsValid(fc_trigForm->tgparentid))
			elog(ERROR, "trigger %u already has a parent trigger",
				 fc_childTrigId);

		fc_trigForm->tgparentid = fc_parentTrigId;

		CatalogTupleUpdate(fc_trigRel, &fc_tuple->t_self, fc_newtup);

		ObjectAddressSet(fc_depender, TriggerRelationId, fc_childTrigId);

		ObjectAddressSet(fc_referenced, TriggerRelationId, fc_parentTrigId);
		recordDependencyOn(&fc_depender, &fc_referenced, DEPENDENCY_PARTITION_PRI);

		ObjectAddressSet(fc_referenced, RelationRelationId, fc_childTableId);
		recordDependencyOn(&fc_depender, &fc_referenced, DEPENDENCY_PARTITION_SEC);
	}
	else
	{
		fc_trigForm->tgparentid = InvalidOid;

		CatalogTupleUpdate(fc_trigRel, &fc_tuple->t_self, fc_newtup);

		deleteDependencyRecordsForClass(TriggerRelationId, fc_childTrigId,
										TriggerRelationId,
										DEPENDENCY_PARTITION_PRI);
		deleteDependencyRecordsForClass(TriggerRelationId, fc_childTrigId,
										RelationRelationId,
										DEPENDENCY_PARTITION_SEC);
	}

	heap_freetuple(fc_newtup);
	systable_endscan(fc_tgscan);
}


/*
 * 触发器删除的内部实现。
 */
void RemoveTriggerById(Oid fc_trigOid)
{
	Relation	fc_tgrel;
	SysScanDesc fc_tgscan;
	ScanKeyData fc_skey[1];
	HeapTuple	fc_tup;
	Oid			fc_relid;
	Relation	fc_rel;

	fc_tgrel = table_open(TriggerRelationId, RowExclusiveLock);

	/*
	 * 找到要删除的触发器。
	 */
	ScanKeyInit(&fc_skey[0],
				Anum_pg_trigger_oid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_trigOid));

	fc_tgscan = systable_beginscan(fc_tgrel, TriggerOidIndexId, true,
								NULL, 1, fc_skey);

	fc_tup = systable_getnext(fc_tgscan);
	if (!HeapTupleIsValid(fc_tup))
		elog(ERROR, "could not find tuple for trigger %u", fc_trigOid);

	/*
	 * 打开并对触发器所属的关系进行排他锁定。
	 */
	fc_relid = ((Form_pg_trigger) GETSTRUCT(fc_tup))->tgrelid;

	fc_rel = table_open(fc_relid, AccessExclusiveLock);

	if (fc_rel->rd_rel->relkind != RELKIND_RELATION &&
		fc_rel->rd_rel->relkind != RELKIND_VIEW &&
		fc_rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
		fc_rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("relation \"%s\" cannot have triggers",
						RelationGetRelationName(fc_rel)),
				 errdetail_relkind_not_supported(fc_rel->rd_rel->relkind)));

	if (!allowSystemTableMods && IsSystemRelation(fc_rel))
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("permission denied: \"%s\" is a system catalog",
						RelationGetRelationName(fc_rel))));

	/*
	 * 删除pg_trigger元组。
	 */
	CatalogTupleDelete(fc_tgrel, &fc_tup->t_self);

	systable_endscan(fc_tgscan);
	table_close(fc_tgrel, RowExclusiveLock);

	/*
	 * 我们不去尝试确定是否还有其他触发器存在，
	 * 这在决定是否可以安全地清除
	 * 关系的relhastriggers时是必要的。（在任何情况下，
	 * 可能有并发进程添加新的触发器。）我们只需强制执行relcache无效以
	 * 使其他后端（包括此后端！）重建其relcache条目。
	 * 即使没有剩余的触发器，保持relhastriggers为真也没有太大危害。
	 */
	CacheInvalidateRelcache(fc_rel);

	/* 在事务结束前保持对触发器关系的锁定 */
	table_close(fc_rel, NoLock);
}

/*
 * get_trigger_oid - 通过名称查找触发器以找到其OID。
 *
 * 如果missing_ok为false，则如果未找到触发器则抛出错误。
 * 如果为true，则仅返回InvalidOid。
 */
Oid get_trigger_oid(Oid fc_relid, const char *fc_trigname, bool fc_missing_ok)
{
	Relation	fc_tgrel;
	ScanKeyData fc_skey[2];
	SysScanDesc fc_tgscan;
	HeapTuple	fc_tup;
	Oid			fc_oid;

	/*
	 * 查找触发器，验证权限，设置对象地址
	 */
	fc_tgrel = table_open(TriggerRelationId, AccessShareLock);

	ScanKeyInit(&fc_skey[0],
				Anum_pg_trigger_tgrelid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_relid));
	ScanKeyInit(&fc_skey[1],
				Anum_pg_trigger_tgname,
				BTEqualStrategyNumber, F_NAMEEQ,
				CStringGetDatum(fc_trigname));

	fc_tgscan = systable_beginscan(fc_tgrel, TriggerRelidNameIndexId, true,
								NULL, 2, fc_skey);

	fc_tup = systable_getnext(fc_tgscan);

	if (!HeapTupleIsValid(fc_tup))
	{
		if (!fc_missing_ok)
			ereport(ERROR,
					(errcode(ERRCODE_UNDEFINED_OBJECT),
					 errmsg("trigger \"%s\" for table \"%s\" does not exist",
							fc_trigname, get_rel_name(fc_relid))));
		fc_oid = InvalidOid;
	}
	else
	{
		fc_oid = ((Form_pg_trigger) GETSTRUCT(fc_tup))->oid;
	}

	systable_endscan(fc_tgscan);
	table_close(fc_tgrel, AccessShareLock);
	return fc_oid;
}

/*
 * 在获取关系锁定之前执行权限和完整性检查。
 */
static void fc_RangeVarCallbackForRenameTrigger(const RangeVar *fc_rv, Oid fc_relid, Oid fc_oldrelid,
								 void *fc_arg)
{
	HeapTuple	fc_tuple;
	Form_pg_class fc_form;

	fc_tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(fc_relid));
	if (!HeapTupleIsValid(fc_tuple))
		return;					/* 并发删除 */
	fc_form = (Form_pg_class) GETSTRUCT(fc_tuple);

	/* 只有表和视图可以有触发器 */
	if (fc_form->relkind != RELKIND_RELATION && fc_form->relkind != RELKIND_VIEW &&
		fc_form->relkind != RELKIND_FOREIGN_TABLE &&
		fc_form->relkind != RELKIND_PARTITIONED_TABLE)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("relation \"%s\" cannot have triggers",
						fc_rv->relname),
				 errdetail_relkind_not_supported(fc_form->relkind)));

	/* 您必须拥有表才能重命名其触发器之一 */
	if (!pg_class_ownercheck(fc_relid, GetUserId()))
		aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(fc_relid)), fc_rv->relname);
	if (!allowSystemTableMods && IsSystemClass(fc_relid, fc_form))
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("permission denied: \"%s\" is a system catalog",
						fc_rv->relname)));

	ReleaseSysCache(fc_tuple);
}

/*
 *		renametrig		- 更改关系上触发器的名称
 *
 *		触发器名称在触发器目录中被更改。
 *		不保留以前名称的记录。
 *
 *		从关系目录中获取适当的 relrelation （如果没有参数）
 *		扫描触发器目录
 *				查找名称冲突（在 rel 内部）
 *				查找原始触发器（如果没有参数）
 *		在触发器元组中修改 tgname
 *		更新目录中的行
 */
ObjectAddress renametrig(RenameStmt *fc_stmt)
{
	Oid			fc_tgoid;
	Relation	fc_targetrel;
	Relation	fc_tgrel;
	HeapTuple	fc_tuple;
	SysScanDesc fc_tgscan;
	ScanKeyData fc_key[2];
	Oid			fc_relid;
	ObjectAddress fc_address;

	/*
	 * 查找名称，检查权限，并获取锁（我们不会在事务结束之前释放这个锁）。
	 */
	fc_relid = RangeVarGetRelidExtended(fc_stmt->relation, AccessExclusiveLock,
									 0,
									 fc_RangeVarCallbackForRenameTrigger,
									 NULL);

	/* 已经持有锁，因此只需构建 relcache 条目。 */
	fc_targetrel = relation_open(fc_relid, NoLock);

	/*
	 * 在分区表上，此操作递归到分区。提前锁定所有表。
	 */
	if (fc_targetrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
		(void) find_all_inheritors(fc_relid, AccessExclusiveLock, NULL);

	fc_tgrel = table_open(TriggerRelationId, RowExclusiveLock);

	/*
	 * 搜索要修改的触发器。
	 */
	ScanKeyInit(&fc_key[0],
				Anum_pg_trigger_tgrelid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_relid));
	ScanKeyInit(&fc_key[1],
				Anum_pg_trigger_tgname,
				BTEqualStrategyNumber, F_NAMEEQ,
				PointerGetDatum(fc_stmt->subname));
	fc_tgscan = systable_beginscan(fc_tgrel, TriggerRelidNameIndexId, true,
								NULL, 2, fc_key);
	if (HeapTupleIsValid(fc_tuple = systable_getnext(fc_tgscan)))
	{
		Form_pg_trigger fc_trigform;

		fc_trigform = (Form_pg_trigger) GETSTRUCT(fc_tuple);
		fc_tgoid = fc_trigform->oid;

		/*
		 * 如果触发器源自父级分区表上的触发器，则拒绝重命名。
		 * 我们不允许分区中的触发器与其父级的名称不同：
		 * 这将导致不一致，pg_dump 将无法重现。
		 */
		if (OidIsValid(fc_trigform->tgparentid))
			ereport(ERROR,
					errmsg("cannot rename trigger \"%s\" on table \"%s\"",
						   fc_stmt->subname, RelationGetRelationName(fc_targetrel)),
					errhint("Rename the trigger on the partitioned table \"%s\" instead.",
							get_rel_name(get_partition_parent(fc_relid, false))));


		/* 在该关系上重命名触发器... */
		fc_renametrig_internal(fc_tgrel, fc_targetrel, fc_tuple, fc_stmt->newname,
							fc_stmt->subname);

		/* ... 如果它是分区的，则递归到其分区 */
		if (fc_targetrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
		{
			PartitionDesc fc_partdesc = RelationGetPartitionDesc(fc_targetrel, true);

			for (int fc_i = 0; fc_i < fc_partdesc->nparts; fc_i++)
			{
				Oid			fc_partitionId = fc_partdesc->oids[fc_i];

				fc_renametrig_partition(fc_tgrel, fc_partitionId, fc_trigform->oid,
									 fc_stmt->newname, fc_stmt->subname);
			}
		}
	}
	else
	{
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_OBJECT),
				 errmsg("trigger \"%s\" for table \"%s\" does not exist",
						fc_stmt->subname, RelationGetRelationName(fc_targetrel))));
	}

	ObjectAddressSet(fc_address, TriggerRelationId, fc_tgoid);

	systable_endscan(fc_tgscan);

	table_close(fc_tgrel, RowExclusiveLock);

	/*
	 * 关闭 rel，但保持独占锁定！
	 */
	relation_close(fc_targetrel, NoLock);

	return fc_address;
}

/*
 * renametrig 的子例程 -- 执行在一个表上重命名一个触发器的实际工作。
 *
 * 如果触发器的名称与预期不同，则发出通知。
 */
static void fc_renametrig_internal(Relation fc_tgrel, Relation fc_targetrel, HeapTuple fc_trigtup,
					const char *fc_newname, const char *fc_expected_name)
{
	HeapTuple	fc_tuple;
	Form_pg_trigger fc_tgform;
	ScanKeyData fc_key[2];
	SysScanDesc fc_tgscan;

	/* 如果触发器已经有了新的名称，则无需操作。 */
	fc_tgform = (Form_pg_trigger) GETSTRUCT(fc_trigtup);
	if (strcmp(NameStr(fc_tgform->tgname), fc_newname) == 0)
		return;

	/*
	 * 在实际尝试重命名之前，搜索具有相同名称的触发器。
	 * 在这种情况下，更新将失败并出现丑陋的消息，更好抛出一个更友好的错误。
	 */
	ScanKeyInit(&fc_key[0],
				Anum_pg_trigger_tgrelid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(RelationGetRelid(fc_targetrel)));
	ScanKeyInit(&fc_key[1],
				Anum_pg_trigger_tgname,
				BTEqualStrategyNumber, F_NAMEEQ,
				PointerGetDatum(fc_newname));
	fc_tgscan = systable_beginscan(fc_tgrel, TriggerRelidNameIndexId, true,
								NULL, 2, fc_key);
	if (HeapTupleIsValid(fc_tuple = systable_getnext(fc_tgscan)))
		ereport(ERROR,
				(errcode(ERRCODE_DUPLICATE_OBJECT),
				 errmsg("trigger \"%s\" for relation \"%s\" already exists",
						fc_newname, RelationGetRelationName(fc_targetrel))));
	systable_endscan(fc_tgscan);

	/*
	 * 目标名称是自由的；用它更新现有的 pg_trigger 元组。
	 */
	fc_tuple = heap_copytuple(fc_trigtup);	/* 需要一个可修改的副本 */
	fc_tgform = (Form_pg_trigger) GETSTRUCT(fc_tuple);

	/*
	 * 如果触发器的名称与我们预期的不同，让用户知道。
	 * （我们可以继续，因为我们必须是根据 tgparentid 链接到达这里的。）
	 */
	if (strcmp(NameStr(fc_tgform->tgname), fc_expected_name) != 0)
		ereport(NOTICE,
				errmsg("renamed trigger \"%s\" on relation \"%s\"",
					   NameStr(fc_tgform->tgname),
					   RelationGetRelationName(fc_targetrel)));

	namestrcpy(&fc_tgform->tgname, fc_newname);

	CatalogTupleUpdate(fc_tgrel, &fc_tuple->t_self, fc_tuple);

	InvokeObjectPostAlterHook(TriggerRelationId, fc_tgform->oid, 0);

	/*
	 * 使关系的 relcache 条目失效，以便其他后端（以及这个后端！）收到 SI 消息以重新构建 relcache 条目。
	 * （理想情况下，这应该自动发生...）
	 */
	CacheInvalidateRelcache(fc_targetrel);
}

/*
 * renametrig 的子例程 -- 在重命名分区表上的触发器时递归到分区的助手。
 */
static void fc_renametrig_partition(Relation fc_tgrel, Oid fc_partitionId, Oid fc_parentTriggerOid,
					 const char *fc_newname, const char *fc_expected_name)
{
	SysScanDesc fc_tgscan;
	ScanKeyData fc_key;
	HeapTuple	fc_tuple;

	/*
	 * 给定一个关系和父关系上触发器的 OID，找到子关系中相应的触发器并将该触发器重命名为给定名称。
	 */
	ScanKeyInit(&fc_key,
				Anum_pg_trigger_tgrelid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_partitionId));
	fc_tgscan = systable_beginscan(fc_tgrel, TriggerRelidNameIndexId, true,
								NULL, 1, &fc_key);
	while (HeapTupleIsValid(fc_tuple = systable_getnext(fc_tgscan)))
	{
		Form_pg_trigger fc_tgform = (Form_pg_trigger) GETSTRUCT(fc_tuple);
		Relation	fc_partitionRel;

		if (fc_tgform->tgparentid != fc_parentTriggerOid)
			continue;			/* 不是我们的触发器 */

		fc_partitionRel = table_open(fc_partitionId, NoLock);

		/* 在该分区上重命名触发器 */
		fc_renametrig_internal(fc_tgrel, fc_partitionRel, fc_tuple, fc_newname, fc_expected_name);

		/* 如果该关系是分区的，则递归到其分区 */
		if (fc_partitionRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
		{
			PartitionDesc fc_partdesc = RelationGetPartitionDesc(fc_partitionRel,
															  true);

			for (int fc_i = 0; fc_i < fc_partdesc->nparts; fc_i++)
			{
				Oid			fc_partitionId = fc_partdesc->oids[fc_i];

				fc_renametrig_partition(fc_tgrel, fc_partitionId, fc_tgform->oid, fc_newname,
									 NameStr(fc_tgform->tgname));
			}
		}
		table_close(fc_partitionRel, NoLock);

		/* 至多应有一个匹配的元组 */
		break;
	}
	systable_endscan(fc_tgscan);
}

/*
 * EnableDisableTrigger()
 *
 *	通过 ALTER TABLE ENABLE/DISABLE [ REPLICA | ALWAYS ] TRIGGER 调用
 *	来更改指定触发器的 'tgenabled' 字段
 *
 * rel: 要处理的关系（调用者必须持有适当的锁）
 * tgname: 要处理的触发器名称，或 NULL 扫描所有触发器
 * tgparent: 如果不为零，仅处理具有此 tgparentid 的触发器
 * fires_when: tgenabled 字段的新值。除了通用
 *			   启用/禁用之外，这还定义了触发器
 *			   在会话复制角色中应何时触发。
 * skip_system: 如果为真，跳过 "system" 触发器（约束触发器）
 * recurse: 如果为真，对分区递归处理
 *
 * 调用者应该检查表的权限；在这里我们还强制要求
 * 更改系统触发器状态需要超级用户权限
 */
void EnableDisableTriggerNew2(Relation fc_rel, const char *fc_tgname, Oid fc_tgparent,
						 char fc_fires_when, bool fc_skip_system, bool fc_recurse,
						 LOCKMODE fc_lockmode)
{
	Relation	fc_tgrel;
	int			fc_nkeys;
	ScanKeyData fc_keys[2];
	SysScanDesc fc_tgscan;
	HeapTuple	fc_tuple;
	bool		fc_found;
	bool		fc_changed;

	/* 扫描 pg_triggers 中相关条目 */
	fc_tgrel = table_open(TriggerRelationId, RowExclusiveLock);

	ScanKeyInit(&fc_keys[0],
				Anum_pg_trigger_tgrelid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(RelationGetRelid(fc_rel)));
	if (fc_tgname)
	{
		ScanKeyInit(&fc_keys[1],
					Anum_pg_trigger_tgname,
					BTEqualStrategyNumber, F_NAMEEQ,
					CStringGetDatum(fc_tgname));
		fc_nkeys = 2;
	}
	else
		fc_nkeys = 1;

	fc_tgscan = systable_beginscan(fc_tgrel, TriggerRelidNameIndexId, true,
								NULL, fc_nkeys, fc_keys);

	fc_found = fc_changed = false;

	while (HeapTupleIsValid(fc_tuple = systable_getnext(fc_tgscan)))
	{
		Form_pg_trigger fc_oldtrig = (Form_pg_trigger) GETSTRUCT(fc_tuple);

		if (OidIsValid(fc_tgparent) && fc_tgparent != fc_oldtrig->tgparentid)
			continue;

		if (fc_oldtrig->tgisinternal)
		{
			/* 系统触发器 ... 可以处理吗？ */
			if (fc_skip_system)
				continue;
			if (!superuser())
				ereport(ERROR,
						(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
						 errmsg("permission denied: \"%s\" is a system trigger",
								NameStr(fc_oldtrig->tgname))));
		}

		fc_found = true;

		if (fc_oldtrig->tgenabled != fc_fires_when)
		{
			/* 需要更改这个 ... 制作一个副本以进行修改 */
			HeapTuple	fc_newtup = heap_copytuple(fc_tuple);
			Form_pg_trigger fc_newtrig = (Form_pg_trigger) GETSTRUCT(fc_newtup);

			fc_newtrig->tgenabled = fc_fires_when;

			CatalogTupleUpdate(fc_tgrel, &fc_newtup->t_self, fc_newtup);

			heap_freetuple(fc_newtup);

			fc_changed = true;
		}

		/*
		 * 在分区表上更改 FOR EACH ROW 触发器时，对分区进行相同操作，
		 * 除非指定了 ONLY。
		 *
		 * 请注意，即使我们上述没有更改触发器，我们也会递归，
		 * 因为分区的触发器副本可能具有不同的 tgenabled 值，
		 * 因此可能需要进行更改。
		 */
		if (fc_recurse &&
			fc_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
			(TRIGGER_FOR_ROW(fc_oldtrig->tgtype)))
		{
			PartitionDesc fc_partdesc = RelationGetPartitionDesc(fc_rel, true);
			int			fc_i;

			for (fc_i = 0; fc_i < fc_partdesc->nparts; fc_i++)
			{
				Relation	fc_part;

				fc_part = relation_open(fc_partdesc->oids[fc_i], fc_lockmode);
				/* 匹配子触发器的 tgparentid，而不是它们的名称 */
				EnableDisableTriggerNew2(fc_part, NULL, fc_oldtrig->oid,
										 fc_fires_when, fc_skip_system, fc_recurse,
										 fc_lockmode);
				table_close(fc_part, NoLock);	/* 保持锁定直到提交 */
			}
		}

		InvokeObjectPostAlterHook(TriggerRelationId,
								  fc_oldtrig->oid, 0);
	}

	systable_endscan(fc_tgscan);

	table_close(fc_tgrel, RowExclusiveLock);

	if (fc_tgname && !fc_found)
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_OBJECT),
				 errmsg("trigger \"%s\" for table \"%s\" does not exist",
						fc_tgname, RelationGetRelationName(fc_rel))));

	/*
	 * 如果我们更改了任何内容，广播 SI inval 消息以强制每个
	 * 后端（包括我们自己！）重建关系的 relcache 条目。
	 * 否则，它们将无法及时应用更改。
	 */
	if (fc_changed)
		CacheInvalidateRelcache(fc_rel);
}

/*
 * ABI 兼容的包装器以模拟上述函数的旧版本。
 * 不要在新代码中调用这些版本。
 */
void EnableDisableTriggerNew(Relation fc_rel, const char *fc_tgname,
						char fc_fires_when, bool fc_skip_system, bool fc_recurse,
						LOCKMODE fc_lockmode)
{
	EnableDisableTriggerNew2(fc_rel, fc_tgname, InvalidOid,
							 fc_fires_when, fc_skip_system,
							 fc_recurse, fc_lockmode);
}

void EnableDisableTrigger(Relation fc_rel, const char *fc_tgname,
					 char fc_fires_when, bool fc_skip_system,
					 LOCKMODE fc_lockmode)
{
	EnableDisableTriggerNew2(fc_rel, fc_tgname, InvalidOid,
							 fc_fires_when, fc_skip_system,
							 true, fc_lockmode);
}


/*
 * 构建要附加到给定 relcache 条目的触发器数据。
 *
 * 请注意，附加到 relcache 条目的触发器数据必须存储在
 * CacheMemoryContext 中，以确保它在 relcache 条目存在期间存活。
 * 但我们应该在一个较短期的工作上下文中运行。为了避免
 * 如果此例程失败而泄漏缓存内存，我们在工作内存中构建一个
 * 临时 TriggerDesc，然后将完成的结构复制到缓存内存中。
 */
void RelationBuildTriggers(Relation fc_relation)
{
	TriggerDesc *fc_trigdesc;
	int			fc_numtrigs;
	int			fc_maxtrigs;
	Trigger    *fc_triggers;
	Relation	fc_tgrel;
	ScanKeyData fc_skey;
	SysScanDesc fc_tgscan;
	HeapTuple	fc_htup;
	MemoryContext fc_oldContext;
	int			fc_i;

	/*
	 * 分配一个工作数组以容纳触发器（如果需要，数组会扩展）
	 */
	fc_maxtrigs = 16;
	fc_triggers = (Trigger *) palloc(fc_maxtrigs * sizeof(Trigger));
	fc_numtrigs = 0;

	/*
	 * 注意：由于我们使用 TriggerRelidNameIndexId 扫描触发器，因此我们将
	 * 按名称顺序读取触发器，除非在紧急恢复操作期间（即忽略系统索引）。
	 * 这反过来确保触发器将按名称顺序触发。
	 */
	ScanKeyInit(&fc_skey,
				Anum_pg_trigger_tgrelid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(RelationGetRelid(fc_relation)));

	fc_tgrel = table_open(TriggerRelationId, AccessShareLock);
	fc_tgscan = systable_beginscan(fc_tgrel, TriggerRelidNameIndexId, true,
								NULL, 1, &fc_skey);

	while (HeapTupleIsValid(fc_htup = systable_getnext(fc_tgscan)))
	{
		Form_pg_trigger fc_pg_trigger = (Form_pg_trigger) GETSTRUCT(fc_htup);
		Trigger    *fc_build;
		Datum		fc_datum;
		bool		fc_isnull;

		if (fc_numtrigs >= fc_maxtrigs)
		{
			fc_maxtrigs *= 2;
			fc_triggers = (Trigger *) repalloc(fc_triggers, fc_maxtrigs * sizeof(Trigger));
		}
		fc_build = &(fc_triggers[fc_numtrigs]);

		fc_build->tgoid = fc_pg_trigger->oid;
		fc_build->tgname = DatumGetCString(DirectFunctionCall1(nameout,
															NameGetDatum(&fc_pg_trigger->tgname)));
		fc_build->tgfoid = fc_pg_trigger->tgfoid;
		fc_build->tgtype = fc_pg_trigger->tgtype;
		fc_build->tgenabled = fc_pg_trigger->tgenabled;
		fc_build->tgisinternal = fc_pg_trigger->tgisinternal;
		fc_build->tgisclone = OidIsValid(fc_pg_trigger->tgparentid);
		fc_build->tgconstrrelid = fc_pg_trigger->tgconstrrelid;
		fc_build->tgconstrindid = fc_pg_trigger->tgconstrindid;
		fc_build->tgconstraint = fc_pg_trigger->tgconstraint;
		fc_build->tgdeferrable = fc_pg_trigger->tgdeferrable;
		fc_build->tginitdeferred = fc_pg_trigger->tginitdeferred;
		fc_build->tgnargs = fc_pg_trigger->tgnargs;
		/* tgattr 是第一个可变宽度字段，因此可以直接访问 */
		fc_build->tgnattr = fc_pg_trigger->tgattr.dim1;
		if (fc_build->tgnattr > 0)
		{
			fc_build->tgattr = (int16 *) palloc(fc_build->tgnattr * sizeof(int16));
			memcpy(fc_build->tgattr, &(fc_pg_trigger->tgattr.values),
				   fc_build->tgnattr * sizeof(int16));
		}
		else
			fc_build->tgattr = NULL;
		if (fc_build->tgnargs > 0)
		{
			bytea	   *fc_val;
			char	   *fc_p;

			fc_val = DatumGetByteaPP(fastgetattr(fc_htup,
											  Anum_pg_trigger_tgargs,
											  fc_tgrel->rd_att, &fc_isnull));
			if (fc_isnull)
				elog(ERROR, "tgargs is null in trigger for relation \"%s\"",
					 RelationGetRelationName(fc_relation));
			fc_p = (char *) VARDATA_ANY(fc_val);
			fc_build->tgargs = (char **) palloc(fc_build->tgnargs * sizeof(char *));
			for (fc_i = 0; fc_i < fc_build->tgnargs; fc_i++)
			{
				fc_build->tgargs[fc_i] = pstrdup(fc_p);
				fc_p += strlen(fc_p) + 1;
			}
		}
		else
			fc_build->tgargs = NULL;

		fc_datum = fastgetattr(fc_htup, Anum_pg_trigger_tgoldtable,
							fc_tgrel->rd_att, &fc_isnull);
		if (!fc_isnull)
			fc_build->tgoldtable =
				DatumGetCString(DirectFunctionCall1(nameout, fc_datum));
		else
			fc_build->tgoldtable = NULL;

		fc_datum = fastgetattr(fc_htup, Anum_pg_trigger_tgnewtable,
							fc_tgrel->rd_att, &fc_isnull);
		if (!fc_isnull)
			fc_build->tgnewtable =
				DatumGetCString(DirectFunctionCall1(nameout, fc_datum));
		else
			fc_build->tgnewtable = NULL;

		fc_datum = fastgetattr(fc_htup, Anum_pg_trigger_tgqual,
							fc_tgrel->rd_att, &fc_isnull);
		if (!fc_isnull)
			fc_build->tgqual = TextDatumGetCString(fc_datum);
		else
			fc_build->tgqual = NULL;

		fc_numtrigs++;
	}

	systable_endscan(fc_tgscan);
	table_close(fc_tgrel, AccessShareLock);

	/* 可能没有任何触发器 */
	if (fc_numtrigs == 0)
	{
		pfree(fc_triggers);
		return;
	}

	/* 构建 trigdesc */
	fc_trigdesc = (TriggerDesc *) palloc0(sizeof(TriggerDesc));
	fc_trigdesc->triggers = fc_triggers;
	fc_trigdesc->numtriggers = fc_numtrigs;
	for (fc_i = 0; fc_i < fc_numtrigs; fc_i++)
		fc_SetTriggerFlags(fc_trigdesc, &(fc_triggers[fc_i]));

	/* 将完成的 trigdesc 复制到缓存存储中 */
	fc_oldContext = MemoryContextSwitchTo(CacheMemoryContext);
	fc_relation->trigdesc = CopyTriggerDesc(fc_trigdesc);
	MemoryContextSwitchTo(fc_oldContext);

	/* 释放工作内存 */
	FreeTriggerDesc(fc_trigdesc);
}

/*
 * 更新 TriggerDesc 的提示标志以包含指定的触发器
 */
static void fc_SetTriggerFlags(TriggerDesc *fc_trigdesc, Trigger *fc_trigger)
{
	int16		fc_tgtype = fc_trigger->tgtype;

	fc_trigdesc->trig_insert_before_row |=
		TRIGGER_TYPE_MATCHES(fc_tgtype, TRIGGER_TYPE_ROW,
							 TRIGGER_TYPE_BEFORE, TRIGGER_TYPE_INSERT);
	fc_trigdesc->trig_insert_after_row |=
		TRIGGER_TYPE_MATCHES(fc_tgtype, TRIGGER_TYPE_ROW,
							 TRIGGER_TYPE_AFTER, TRIGGER_TYPE_INSERT);
	fc_trigdesc->trig_insert_instead_row |=
		TRIGGER_TYPE_MATCHES(fc_tgtype, TRIGGER_TYPE_ROW,
							 TRIGGER_TYPE_INSTEAD, TRIGGER_TYPE_INSERT);
	fc_trigdesc->trig_insert_before_statement |=
		TRIGGER_TYPE_MATCHES(fc_tgtype, TRIGGER_TYPE_STATEMENT,
							 TRIGGER_TYPE_BEFORE, TRIGGER_TYPE_INSERT);
	fc_trigdesc->trig_insert_after_statement |=
		TRIGGER_TYPE_MATCHES(fc_tgtype, TRIGGER_TYPE_STATEMENT,
							 TRIGGER_TYPE_AFTER, TRIGGER_TYPE_INSERT);
	fc_trigdesc->trig_update_before_row |=
		TRIGGER_TYPE_MATCHES(fc_tgtype, TRIGGER_TYPE_ROW,
							 TRIGGER_TYPE_BEFORE, TRIGGER_TYPE_UPDATE);
	fc_trigdesc->trig_update_after_row |=
		TRIGGER_TYPE_MATCHES(fc_tgtype, TRIGGER_TYPE_ROW,
							 TRIGGER_TYPE_AFTER, TRIGGER_TYPE_UPDATE);
	fc_trigdesc->trig_update_instead_row |=
		TRIGGER_TYPE_MATCHES(fc_tgtype, TRIGGER_TYPE_ROW,
							 TRIGGER_TYPE_INSTEAD, TRIGGER_TYPE_UPDATE);
	fc_trigdesc->trig_update_before_statement |=
		TRIGGER_TYPE_MATCHES(fc_tgtype, TRIGGER_TYPE_STATEMENT,
							 TRIGGER_TYPE_BEFORE, TRIGGER_TYPE_UPDATE);
	fc_trigdesc->trig_update_after_statement |=
		TRIGGER_TYPE_MATCHES(fc_tgtype, TRIGGER_TYPE_STATEMENT,
							 TRIGGER_TYPE_AFTER, TRIGGER_TYPE_UPDATE);
	fc_trigdesc->trig_delete_before_row |=
		TRIGGER_TYPE_MATCHES(fc_tgtype, TRIGGER_TYPE_ROW,
							 TRIGGER_TYPE_BEFORE, TRIGGER_TYPE_DELETE);
	fc_trigdesc->trig_delete_after_row |=
		TRIGGER_TYPE_MATCHES(fc_tgtype, TRIGGER_TYPE_ROW,
							 TRIGGER_TYPE_AFTER, TRIGGER_TYPE_DELETE);
	fc_trigdesc->trig_delete_instead_row |=
		TRIGGER_TYPE_MATCHES(fc_tgtype, TRIGGER_TYPE_ROW,
							 TRIGGER_TYPE_INSTEAD, TRIGGER_TYPE_DELETE);
	fc_trigdesc->trig_delete_before_statement |=
		TRIGGER_TYPE_MATCHES(fc_tgtype, TRIGGER_TYPE_STATEMENT,
							 TRIGGER_TYPE_BEFORE, TRIGGER_TYPE_DELETE);
	fc_trigdesc->trig_delete_after_statement |=
		TRIGGER_TYPE_MATCHES(fc_tgtype, TRIGGER_TYPE_STATEMENT,
							 TRIGGER_TYPE_AFTER, TRIGGER_TYPE_DELETE);
	/* 没有行级截断触发器 */
	fc_trigdesc->trig_truncate_before_statement |=
		TRIGGER_TYPE_MATCHES(fc_tgtype, TRIGGER_TYPE_STATEMENT,
							 TRIGGER_TYPE_BEFORE, TRIGGER_TYPE_TRUNCATE);
	fc_trigdesc->trig_truncate_after_statement |=
		TRIGGER_TYPE_MATCHES(fc_tgtype, TRIGGER_TYPE_STATEMENT,
							 TRIGGER_TYPE_AFTER, TRIGGER_TYPE_TRUNCATE);

	fc_trigdesc->trig_insert_new_table |=
		(TRIGGER_FOR_INSERT(fc_tgtype) &&
		 TRIGGER_USES_TRANSITION_TABLE(fc_trigger->tgnewtable));
	fc_trigdesc->trig_update_old_table |=
		(TRIGGER_FOR_UPDATE(fc_tgtype) &&
		 TRIGGER_USES_TRANSITION_TABLE(fc_trigger->tgoldtable));
	fc_trigdesc->trig_update_new_table |=
		(TRIGGER_FOR_UPDATE(fc_tgtype) &&
		 TRIGGER_USES_TRANSITION_TABLE(fc_trigger->tgnewtable));
	fc_trigdesc->trig_delete_old_table |=
		(TRIGGER_FOR_DELETE(fc_tgtype) &&
		 TRIGGER_USES_TRANSITION_TABLE(fc_trigger->tgoldtable));
}

/*
 * 复制一个 TriggerDesc 数据结构。
 *
 * 复制在当前内存上下文中分配。
 */
TriggerDesc *
CopyTriggerDesc(TriggerDesc *fc_trigdesc)
{
	TriggerDesc *fc_newdesc;
	Trigger    *fc_trigger;
	int			fc_i;

	if (fc_trigdesc == NULL || fc_trigdesc->numtriggers <= 0)
		return NULL;

	fc_newdesc = (TriggerDesc *) palloc(sizeof(TriggerDesc));
	memcpy(fc_newdesc, fc_trigdesc, sizeof(TriggerDesc));

	fc_trigger = (Trigger *) palloc(fc_trigdesc->numtriggers * sizeof(Trigger));
	memcpy(fc_trigger, fc_trigdesc->triggers,
		   fc_trigdesc->numtriggers * sizeof(Trigger));
	fc_newdesc->triggers = fc_trigger;

	for (fc_i = 0; fc_i < fc_trigdesc->numtriggers; fc_i++)
	{
		fc_trigger->tgname = pstrdup(fc_trigger->tgname);
		if (fc_trigger->tgnattr > 0)
		{
			int16	   *fc_newattr;

			fc_newattr = (int16 *) palloc(fc_trigger->tgnattr * sizeof(int16));
			memcpy(fc_newattr, fc_trigger->tgattr,
				   fc_trigger->tgnattr * sizeof(int16));
			fc_trigger->tgattr = fc_newattr;
		}
		if (fc_trigger->tgnargs > 0)
		{
			char	  **fc_newargs;
			int16		fc_j;

			fc_newargs = (char **) palloc(fc_trigger->tgnargs * sizeof(char *));
			for (fc_j = 0; fc_j < fc_trigger->tgnargs; fc_j++)
				fc_newargs[fc_j] = pstrdup(fc_trigger->tgargs[fc_j]);
			fc_trigger->tgargs = fc_newargs;
		}
		if (fc_trigger->tgqual)
			fc_trigger->tgqual = pstrdup(fc_trigger->tgqual);
		if (fc_trigger->tgoldtable)
			fc_trigger->tgoldtable = pstrdup(fc_trigger->tgoldtable);
		if (fc_trigger->tgnewtable)
			fc_trigger->tgnewtable = pstrdup(fc_trigger->tgnewtable);
		fc_trigger++;
	}

	return fc_newdesc;
}

/*
 * 释放一个 TriggerDesc 数据结构。
 */
void FreeTriggerDesc(TriggerDesc *fc_trigdesc)
{
	Trigger    *fc_trigger;
	int			fc_i;

	if (fc_trigdesc == NULL)
		return;

	fc_trigger = fc_trigdesc->triggers;
	for (fc_i = 0; fc_i < fc_trigdesc->numtriggers; fc_i++)
	{
		pfree(fc_trigger->tgname);
		if (fc_trigger->tgnattr > 0)
			pfree(fc_trigger->tgattr);
		if (fc_trigger->tgnargs > 0)
		{
			while (--(fc_trigger->tgnargs) >= 0)
				pfree(fc_trigger->tgargs[fc_trigger->tgnargs]);
			pfree(fc_trigger->tgargs);
		}
		if (fc_trigger->tgqual)
			pfree(fc_trigger->tgqual);
		if (fc_trigger->tgoldtable)
			pfree(fc_trigger->tgoldtable);
		if (fc_trigger->tgnewtable)
			pfree(fc_trigger->tgnewtable);
		fc_trigger++;
	}
	pfree(fc_trigdesc->triggers);
	pfree(fc_trigdesc);
}

/*
 * 比较两个 TriggerDesc 结构的逻辑相等性。
 */
#ifdef NOT_USED
bool equalTriggerDescs(TriggerDesc *fc_trigdesc1, TriggerDesc *fc_trigdesc2)
{
	int			fc_i,
				fc_j;

	/*
	 * 我们不需要检查提示标志，只需检查触发器数组本身；如果
	 * 我们有相同类型的相同触发器，标志应该匹配。
	 *
	 * 从 7.3 开始，我们假设触发器集合的顺序在
	 * 比较中具有重要意义；因此我们只比较两个集合的对应槽。
	 *
	 * 注意：比较 WHEN 子句的 stringToNode 形式意味着
	 * 解析列位置会影响结果。只要此函数仅用于检测精确相等性，
	 * 比如在检查缓存条目的过时性时，这就可以。
	 */
	if (fc_trigdesc1 != NULL)
	{
		if (fc_trigdesc2 == NULL)
			return false;
		if (fc_trigdesc1->numtriggers != fc_trigdesc2->numtriggers)
			return false;
		for (fc_i = 0; fc_i < fc_trigdesc1->numtriggers; fc_i++)
		{
			Trigger    *fc_trig1 = fc_trigdesc1->triggers + fc_i;
			Trigger    *fc_trig2 = fc_trigdesc2->triggers + fc_i;

			if (fc_trig1->tgoid != fc_trig2->tgoid)
				return false;
			if (strcmp(fc_trig1->tgname, fc_trig2->tgname) != 0)
				return false;
			if (fc_trig1->tgfoid != fc_trig2->tgfoid)
				return false;
			if (fc_trig1->tgtype != fc_trig2->tgtype)
				return false;
			if (fc_trig1->tgenabled != fc_trig2->tgenabled)
				return false;
			if (fc_trig1->tgisinternal != fc_trig2->tgisinternal)
				return false;
			if (fc_trig1->tgisclone != fc_trig2->tgisclone)
				return false;
			if (fc_trig1->tgconstrrelid != fc_trig2->tgconstrrelid)
				return false;
			if (fc_trig1->tgconstrindid != fc_trig2->tgconstrindid)
				return false;
			if (fc_trig1->tgconstraint != fc_trig2->tgconstraint)
				return false;
			if (fc_trig1->tgdeferrable != fc_trig2->tgdeferrable)
				return false;
			if (fc_trig1->tginitdeferred != fc_trig2->tginitdeferred)
				return false;
			if (fc_trig1->tgnargs != fc_trig2->tgnargs)
				return false;
			if (fc_trig1->tgnattr != fc_trig2->tgnattr)
				return false;
			if (fc_trig1->tgnattr > 0 &&
				memcmp(fc_trig1->tgattr, fc_trig2->tgattr,
					   fc_trig1->tgnattr * sizeof(int16)) != 0)
				return false;
			for (fc_j = 0; fc_j < fc_trig1->tgnargs; fc_j++)
				if (strcmp(fc_trig1->tgargs[fc_j], fc_trig2->tgargs[fc_j]) != 0)
					return false;
			if (fc_trig1->tgqual == NULL && fc_trig2->tgqual == NULL)
				 /* ok */ ;
			else if (fc_trig1->tgqual == NULL || fc_trig2->tgqual == NULL)
				return false;
			else if (strcmp(fc_trig1->tgqual, fc_trig2->tgqual) != 0)
				return false;
			if (fc_trig1->tgoldtable == NULL && fc_trig2->tgoldtable == NULL)
				 /* ok */ ;
			else if (fc_trig1->tgoldtable == NULL || fc_trig2->tgoldtable == NULL)
				return false;
			else if (strcmp(fc_trig1->tgoldtable, fc_trig2->tgoldtable) != 0)
				return false;
			if (fc_trig1->tgnewtable == NULL && fc_trig2->tgnewtable == NULL)
				 /* ok */ ;
			else if (fc_trig1->tgnewtable == NULL || fc_trig2->tgnewtable == NULL)
				return false;
			else if (strcmp(fc_trig1->tgnewtable, fc_trig2->tgnewtable) != 0)
				return false;
		}
	}
	else if (fc_trigdesc2 != NULL)
		return false;
	return true;
}
#endif							/* 在较旧的 Perl 上值得怀疑... */

/*
 * 检查是否存在具有过渡表的行级触发器，该触发器会阻止
 * 表成为继承子类或分区。返回第一个不兼容触发器的名称，
 * 如果不存在则返回 NULL。
 */
const char * FindTriggerIncompatibleWithInheritance(TriggerDesc *fc_trigdesc)
{
	if (fc_trigdesc != NULL)
	{
		int			fc_i;

		for (fc_i = 0; fc_i < fc_trigdesc->numtriggers; ++fc_i)
		{
			Trigger    *fc_trigger = &fc_trigdesc->triggers[fc_i];

			if (fc_trigger->tgoldtable != NULL || fc_trigger->tgnewtable != NULL)
				return fc_trigger->tgname;
		}
	}

	return NULL;
}

/*
 * 调用触发器函数。
 *
 *		trigdata: 触发器描述符。
 *		tgindx: 触发器在 finfo 和 instr 数组中的索引。
 *		finfo: 缓存的触发器函数调用信息数组。
 *		instr: 可选的 EXPLAIN ANALYZE 工具状态数组。
 *		per_tuple_context: 执行函数的内存上下文。
 *
 * 返回函数返回的元组（或 NULL）。
 */
static HeapTuple fc_ExecCallTriggerFunc(TriggerData *fc_trigdata,
					int fc_tgindx,
					FmgrInfo *fc_finfo,
					Instrumentation *fc_instr,
					MemoryContext fc_per_tuple_context)
{
	LOCAL_FCINFO(fc_fcinfo, 0);
	PgStat_FunctionCallUsage fc_fcusage;
	Datum		fc_result;
	MemoryContext fc_oldContext;

	/*
	 * 防止代码路径未能初始化过渡表的信息。
	 */
	Assert(((TRIGGER_FIRED_BY_INSERT(fc_trigdata->tg_event) ||
			 TRIGGER_FIRED_BY_UPDATE(fc_trigdata->tg_event) ||
			 TRIGGER_FIRED_BY_DELETE(fc_trigdata->tg_event)) &&
			TRIGGER_FIRED_AFTER(fc_trigdata->tg_event) &&
			!(fc_trigdata->tg_event & AFTER_TRIGGER_DEFERRABLE) &&
			!(fc_trigdata->tg_event & AFTER_TRIGGER_INITDEFERRED)) ||
		   (fc_trigdata->tg_oldtable == NULL && fc_trigdata->tg_newtable == NULL));

	fc_finfo += fc_tgindx;

	/*
	 * 我们缓存 fmgr 查找信息，以避免在每次调用时再次进行查找。
	 */
	if (fc_finfo->fn_oid == InvalidOid)
		fmgr_info(fc_trigdata->tg_trigger->tgfoid, fc_finfo);

	Assert(fc_finfo->fn_oid == fc_trigdata->tg_trigger->tgfoid);

	/*
	 * 如果正在进行 EXPLAIN ANALYZE，开始为此触发器计时。
	 */
	if (fc_instr)
		InstrStartNode(fc_instr + fc_tgindx);

	/*
	 * 在每个元组内存上下文中进行函数评估，以便
	 * 泄漏的内存将在每个元组处理后被回收。特别需要注意的是，
	 * 触发器函数创建的任何新元组将存活到元组周期结束。
	 */
	fc_oldContext = MemoryContextSwitchTo(fc_per_tuple_context);

	/*
	 * 调用函数，不传递任何参数，但设置一个上下文。
	 */
	InitFunctionCallInfoData(*fc_fcinfo, fc_finfo, 0,
							 InvalidOid, (Node *) fc_trigdata, NULL);

	pgstat_init_function_usage(fc_fcinfo, &fc_fcusage);

	MyTriggerDepth++;
	PG_TRY();
	{
		fc_result = FunctionCallInvoke(fc_fcinfo);
	}
	PG_FINALLY();
	{
		MyTriggerDepth--;
	}
	PG_END_TRY();

	pgstat_end_function_usage(&fc_fcusage, true);

	MemoryContextSwitchTo(fc_oldContext);

	/*
	 * 触发器协议允许函数返回空指针，但不得
	 * 设置 isnull 结果标志。
	 */
	if (fc_fcinfo->isnull)
		ereport(ERROR,
				(errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
				 errmsg("trigger function %u returned null value",
						fc_fcinfo->flinfo->fn_oid)));

	/*
	 * 如果正在进行 EXPLAIN ANALYZE，停止为此触发器计时，并且计算
	 * 一次“返回的元组”（实际上是触发次数）。
	 */
	if (fc_instr)
		InstrStopNode(fc_instr + fc_tgindx, 1);

	return (HeapTuple) DatumGetPointer(fc_result);
}

void ExecBSInsertTriggers(EState *fc_estate, ResultRelInfo *fc_relinfo)
{
	TriggerDesc *fc_trigdesc;
	int			fc_i;
	TriggerData fc_LocTriggerData = {0};

	fc_trigdesc = fc_relinfo->ri_TrigDesc;

	if (fc_trigdesc == NULL)
		return;
	if (!fc_trigdesc->trig_insert_before_statement)
		return;

	/* 如果我们在此上下文中已经触发了 BS 触发器则无操作 */
	if (fc_before_stmt_triggers_fired(RelationGetRelid(fc_relinfo->ri_RelationDesc),
								   CMD_INSERT))
		return;

	fc_LocTriggerData.type = T_TriggerData;
	fc_LocTriggerData.tg_event = TRIGGER_EVENT_INSERT |
		TRIGGER_EVENT_BEFORE;
	fc_LocTriggerData.tg_relation = fc_relinfo->ri_RelationDesc;
	for (fc_i = 0; fc_i < fc_trigdesc->numtriggers; fc_i++)
	{
		Trigger    *fc_trigger = &fc_trigdesc->triggers[fc_i];
		HeapTuple	fc_newtuple;

		if (!TRIGGER_TYPE_MATCHES(fc_trigger->tgtype,
								  TRIGGER_TYPE_STATEMENT,
								  TRIGGER_TYPE_BEFORE,
								  TRIGGER_TYPE_INSERT))
			continue;
		if (!fc_TriggerEnabled(fc_estate, fc_relinfo, fc_trigger, fc_LocTriggerData.tg_event,
							NULL, NULL, NULL))
			continue;

		fc_LocTriggerData.tg_trigger = fc_trigger;
		fc_newtuple = fc_ExecCallTriggerFunc(&fc_LocTriggerData,
									   fc_i,
									   fc_relinfo->ri_TrigFunctions,
									   fc_relinfo->ri_TrigInstrument,
									   GetPerTupleMemoryContext(fc_estate));

		if (fc_newtuple)
			ereport(ERROR,
					(errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
					 errmsg("BEFORE STATEMENT trigger cannot return a value")));
	}
}

void ExecASInsertTriggers(EState *fc_estate, ResultRelInfo *fc_relinfo,
					 TransitionCaptureState *fc_transition_capture)
{
	TriggerDesc *fc_trigdesc = fc_relinfo->ri_TrigDesc;

	if (fc_trigdesc && fc_trigdesc->trig_insert_after_statement)
		fc_AfterTriggerSaveEvent(fc_estate, fc_relinfo, NULL, NULL,
							  TRIGGER_EVENT_INSERT,
							  false, NULL, NULL, NIL, NULL, fc_transition_capture,
							  false);
}

bool ExecBRInsertTriggers(EState *fc_estate, ResultRelInfo *fc_relinfo,
					 TupleTableSlot *fc_slot)
{
	TriggerDesc *fc_trigdesc = fc_relinfo->ri_TrigDesc;
	HeapTuple	fc_newtuple = NULL;
	bool		fc_should_free;
	TriggerData fc_LocTriggerData = {0};
	int			fc_i;

	fc_LocTriggerData.type = T_TriggerData;
	fc_LocTriggerData.tg_event = TRIGGER_EVENT_INSERT |
		TRIGGER_EVENT_ROW |
		TRIGGER_EVENT_BEFORE;
	fc_LocTriggerData.tg_relation = fc_relinfo->ri_RelationDesc;
	for (fc_i = 0; fc_i < fc_trigdesc->numtriggers; fc_i++)
	{
		Trigger    *fc_trigger = &fc_trigdesc->triggers[fc_i];
		HeapTuple	fc_oldtuple;

		if (!TRIGGER_TYPE_MATCHES(fc_trigger->tgtype,
								  TRIGGER_TYPE_ROW,
								  TRIGGER_TYPE_BEFORE,
								  TRIGGER_TYPE_INSERT))
			continue;
		if (!fc_TriggerEnabled(fc_estate, fc_relinfo, fc_trigger, fc_LocTriggerData.tg_event,
							NULL, NULL, fc_slot))
			continue;

		if (!fc_newtuple)
			fc_newtuple = ExecFetchSlotHeapTuple(fc_slot, true, &fc_should_free);

		fc_LocTriggerData.tg_trigslot = fc_slot;
		fc_LocTriggerData.tg_trigtuple = fc_oldtuple = fc_newtuple;
		fc_LocTriggerData.tg_trigger = fc_trigger;
		fc_newtuple = fc_ExecCallTriggerFunc(&fc_LocTriggerData,
									   fc_i,
									   fc_relinfo->ri_TrigFunctions,
									   fc_relinfo->ri_TrigInstrument,
									   GetPerTupleMemoryContext(fc_estate));
		if (fc_newtuple == NULL)
		{
			if (fc_should_free)
				heap_freetuple(fc_oldtuple);
			return false;		/* "不做任何操作" */
		}
		else if (fc_newtuple != fc_oldtuple)
		{
			ExecForceStoreHeapTuple(fc_newtuple, fc_slot, false);

			/*
			 * 在分区中的元组经过触发器后，用户
			 * 可能已经更改了分区键，以至于元组不再适合该分区。
			 * 验证这一点。
			 */
			if (fc_trigger->tgisclone &&
				!ExecPartitionCheck(fc_relinfo, fc_slot, fc_estate, false))
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("moving row to another partition during a BEFORE FOR EACH ROW trigger is not supported"),
						 errdetail("Before executing trigger \"%s\", the row was to be in partition \"%s.%s\".",
								   fc_trigger->tgname,
								   get_namespace_name(RelationGetNamespace(fc_relinfo->ri_RelationDesc)),
								   RelationGetRelationName(fc_relinfo->ri_RelationDesc))));

			if (fc_should_free)
				heap_freetuple(fc_oldtuple);

			/* 如果使用，则信号元组应重新获取 */
			fc_newtuple = NULL;
		}
	}

	return true;
}

void ExecARInsertTriggers(EState *fc_estate, ResultRelInfo *fc_relinfo,
					 TupleTableSlot *fc_slot, List *fc_recheckIndexes,
					 TransitionCaptureState *fc_transition_capture)
{
	TriggerDesc *fc_trigdesc = fc_relinfo->ri_TrigDesc;

	if ((fc_trigdesc && fc_trigdesc->trig_insert_after_row) ||
		(fc_transition_capture && fc_transition_capture->tcs_insert_new_table))
		fc_AfterTriggerSaveEvent(fc_estate, fc_relinfo, NULL, NULL,
							  TRIGGER_EVENT_INSERT,
							  true, NULL, fc_slot,
							  fc_recheckIndexes, NULL,
							  fc_transition_capture,
							  false);
}

bool ExecIRInsertTriggers(EState *fc_estate, ResultRelInfo *fc_relinfo,
					 TupleTableSlot *fc_slot)
{
	TriggerDesc *fc_trigdesc = fc_relinfo->ri_TrigDesc;
	HeapTuple	fc_newtuple = NULL;
	bool		fc_should_free;
	TriggerData fc_LocTriggerData = {0};
	int			fc_i;

	fc_LocTriggerData.type = T_TriggerData;
	fc_LocTriggerData.tg_event = TRIGGER_EVENT_INSERT |
		TRIGGER_EVENT_ROW |
		TRIGGER_EVENT_INSTEAD;
	fc_LocTriggerData.tg_relation = fc_relinfo->ri_RelationDesc;
	for (fc_i = 0; fc_i < fc_trigdesc->numtriggers; fc_i++)
	{
		Trigger    *fc_trigger = &fc_trigdesc->triggers[fc_i];
		HeapTuple	fc_oldtuple;

		if (!TRIGGER_TYPE_MATCHES(fc_trigger->tgtype,
								  TRIGGER_TYPE_ROW,
								  TRIGGER_TYPE_INSTEAD,
								  TRIGGER_TYPE_INSERT))
			continue;
		if (!fc_TriggerEnabled(fc_estate, fc_relinfo, fc_trigger, fc_LocTriggerData.tg_event,
							NULL, NULL, fc_slot))
			continue;

		if (!fc_newtuple)
			fc_newtuple = ExecFetchSlotHeapTuple(fc_slot, true, &fc_should_free);

		fc_LocTriggerData.tg_trigslot = fc_slot;
		fc_LocTriggerData.tg_trigtuple = fc_oldtuple = fc_newtuple;
		fc_LocTriggerData.tg_trigger = fc_trigger;
		fc_newtuple = fc_ExecCallTriggerFunc(&fc_LocTriggerData,
									   fc_i,
									   fc_relinfo->ri_TrigFunctions,
									   fc_relinfo->ri_TrigInstrument,
									   GetPerTupleMemoryContext(fc_estate));
		if (fc_newtuple == NULL)
		{
			if (fc_should_free)
				heap_freetuple(fc_oldtuple);
			return false;		/* "不做任何操作" */
		}
		else if (fc_newtuple != fc_oldtuple)
		{
			ExecForceStoreHeapTuple(fc_newtuple, fc_slot, false);

			if (fc_should_free)
				heap_freetuple(fc_oldtuple);

			/* 如果使用，则信号元组应重新获取 */
			fc_newtuple = NULL;
		}
	}

	return true;
}

void ExecBSDeleteTriggers(EState *fc_estate, ResultRelInfo *fc_relinfo)
{
	TriggerDesc *fc_trigdesc;
	int			fc_i;
	TriggerData fc_LocTriggerData = {0};

	fc_trigdesc = fc_relinfo->ri_TrigDesc;

	if (fc_trigdesc == NULL)
		return;
	if (!fc_trigdesc->trig_delete_before_statement)
		return;

	/* 如果我们在此上下文中已经触发了 BS 触发器则无操作 */
	if (fc_before_stmt_triggers_fired(RelationGetRelid(fc_relinfo->ri_RelationDesc),
								   CMD_DELETE))
		return;

	fc_LocTriggerData.type = T_TriggerData;
	fc_LocTriggerData.tg_event = TRIGGER_EVENT_DELETE |
		TRIGGER_EVENT_BEFORE;
	fc_LocTriggerData.tg_relation = fc_relinfo->ri_RelationDesc;
	for (fc_i = 0; fc_i < fc_trigdesc->numtriggers; fc_i++)
	{
		Trigger    *fc_trigger = &fc_trigdesc->triggers[fc_i];
		HeapTuple	fc_newtuple;

		if (!TRIGGER_TYPE_MATCHES(fc_trigger->tgtype,
								  TRIGGER_TYPE_STATEMENT,
								  TRIGGER_TYPE_BEFORE,
								  TRIGGER_TYPE_DELETE))
			continue;
		if (!fc_TriggerEnabled(fc_estate, fc_relinfo, fc_trigger, fc_LocTriggerData.tg_event,
							NULL, NULL, NULL))
			continue;

		fc_LocTriggerData.tg_trigger = fc_trigger;
		fc_newtuple = fc_ExecCallTriggerFunc(&fc_LocTriggerData,
									   fc_i,
									   fc_relinfo->ri_TrigFunctions,
									   fc_relinfo->ri_TrigInstrument,
									   GetPerTupleMemoryContext(fc_estate));

		if (fc_newtuple)
			ereport(ERROR,
					(errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
					 errmsg("BEFORE STATEMENT trigger cannot return a value")));
	}
}

void ExecASDeleteTriggers(EState *fc_estate, ResultRelInfo *fc_relinfo,
					 TransitionCaptureState *fc_transition_capture)
{
	TriggerDesc *fc_trigdesc = fc_relinfo->ri_TrigDesc;

	if (fc_trigdesc && fc_trigdesc->trig_delete_after_statement)
		fc_AfterTriggerSaveEvent(fc_estate, fc_relinfo, NULL, NULL,
							  TRIGGER_EVENT_DELETE,
							  false, NULL, NULL, NIL, NULL, fc_transition_capture,
							  false);
}

/*
 * 执行在行删除之前的触发器。
 *
 * True 表示调用者可以继续删除。False 表示调用者
 * 需要抑制删除，并且如果要求，还需要返回
 * 并发更新的元组（如果有的话）。
 */
bool ExecBRDeleteTriggersNew(EState *fc_estate, EPQState *fc_epqstate,
						ResultRelInfo *fc_relinfo,
						ItemPointer fc_tupleid,
						HeapTuple fc_fdw_trigtuple,
						TupleTableSlot **fc_epqslot,
						TM_Result *fc_tmresult,
						TM_FailureData *fc_tmfd)
{
	TupleTableSlot *fc_slot = ExecGetTriggerOldSlot(fc_estate, fc_relinfo);
	TriggerDesc *fc_trigdesc = fc_relinfo->ri_TrigDesc;
	bool		fc_result = true;
	TriggerData fc_LocTriggerData = {0};
	HeapTuple	fc_trigtuple;
	bool		fc_should_free = false;
	int			fc_i;

	Assert(HeapTupleIsValid(fc_fdw_trigtuple) ^ ItemPointerIsValid(fc_tupleid));
	if (fc_fdw_trigtuple == NULL)
	{
		TupleTableSlot *fc_epqslot_candidate = NULL;

		if (!fc_GetTupleForTrigger(fc_estate, fc_epqstate, fc_relinfo, fc_tupleid,
								LockTupleExclusive, fc_slot, &fc_epqslot_candidate,
								fc_tmresult, fc_tmfd))
			return false;

		/*
		 * 如果元组被并发更新，并且此
		 * 函数的调用者请求更新的元组，则跳过触发器
		 * 执行。
		 */
		if (fc_epqslot_candidate != NULL && fc_epqslot != NULL)
		{
			*fc_epqslot = fc_epqslot_candidate;
			return false;
		}

		fc_trigtuple = ExecFetchSlotHeapTuple(fc_slot, true, &fc_should_free);
	}
	else
	{
		fc_trigtuple = fc_fdw_trigtuple;
		ExecForceStoreHeapTuple(fc_trigtuple, fc_slot, false);
	}

	fc_LocTriggerData.type = T_TriggerData;
	fc_LocTriggerData.tg_event = TRIGGER_EVENT_DELETE |
		TRIGGER_EVENT_ROW |
		TRIGGER_EVENT_BEFORE;
	fc_LocTriggerData.tg_relation = fc_relinfo->ri_RelationDesc;
	for (fc_i = 0; fc_i < fc_trigdesc->numtriggers; fc_i++)
	{
		HeapTuple	fc_newtuple;
		Trigger    *fc_trigger = &fc_trigdesc->triggers[fc_i];

		if (!TRIGGER_TYPE_MATCHES(fc_trigger->tgtype,
								  TRIGGER_TYPE_ROW,
								  TRIGGER_TYPE_BEFORE,
								  TRIGGER_TYPE_DELETE))
			continue;
		if (!fc_TriggerEnabled(fc_estate, fc_relinfo, fc_trigger, fc_LocTriggerData.tg_event,
							NULL, fc_slot, NULL))
			continue;

		fc_LocTriggerData.tg_trigslot = fc_slot;
		fc_LocTriggerData.tg_trigtuple = fc_trigtuple;
		fc_LocTriggerData.tg_trigger = fc_trigger;
		fc_newtuple = fc_ExecCallTriggerFunc(&fc_LocTriggerData,
									   fc_i,
									   fc_relinfo->ri_TrigFunctions,
									   fc_relinfo->ri_TrigInstrument,
									   GetPerTupleMemoryContext(fc_estate));
		if (fc_newtuple == NULL)
		{
			fc_result = false;		/* 告诉调用者抑制删除 */
			break;
		}
		if (fc_newtuple != fc_trigtuple)
			heap_freetuple(fc_newtuple);
	}
	if (fc_should_free)
		heap_freetuple(fc_trigtuple);

	return fc_result;
}

/*
 * ABI 兼容的包装器，用于模拟上述函数的旧版本。
 * 请勿在新代码中调用此版本。
 */
bool ExecBRDeleteTriggers(EState *fc_estate, EPQState *fc_epqstate,
					 ResultRelInfo *fc_relinfo,
					 ItemPointer fc_tupleid,
					 HeapTuple fc_fdw_trigtuple,
					 TupleTableSlot **fc_epqslot)
{
	return ExecBRDeleteTriggersNew(fc_estate, fc_epqstate, fc_relinfo, fc_tupleid,
								   fc_fdw_trigtuple, fc_epqslot, NULL, NULL);
}

/*
 * 注意：如果 DELETE 作为跨分区更新的一部分进行，
 * is_crosspart_update 必须为 true。
 */
void ExecARDeleteTriggers(EState *fc_estate,
					 ResultRelInfo *fc_relinfo,
					 ItemPointer fc_tupleid,
					 HeapTuple fc_fdw_trigtuple,
					 TransitionCaptureState *fc_transition_capture,
					 bool fc_is_crosspart_update)
{
	TriggerDesc *fc_trigdesc = fc_relinfo->ri_TrigDesc;

	if ((fc_trigdesc && fc_trigdesc->trig_delete_after_row) ||
		(fc_transition_capture && fc_transition_capture->tcs_delete_old_table))
	{
		TupleTableSlot *fc_slot = ExecGetTriggerOldSlot(fc_estate, fc_relinfo);

		Assert(HeapTupleIsValid(fc_fdw_trigtuple) ^ ItemPointerIsValid(fc_tupleid));
		if (fc_fdw_trigtuple == NULL)
			fc_GetTupleForTrigger(fc_estate,
							   NULL,
							   fc_relinfo,
							   fc_tupleid,
							   LockTupleExclusive,
							   fc_slot,
							   NULL,
							   NULL,
							   NULL);
		else
			ExecForceStoreHeapTuple(fc_fdw_trigtuple, fc_slot, false);

		fc_AfterTriggerSaveEvent(fc_estate, fc_relinfo, NULL, NULL,
							  TRIGGER_EVENT_DELETE,
							  true, fc_slot, NULL, NIL, NULL,
							  fc_transition_capture,
							  fc_is_crosspart_update);
	}
}

bool ExecIRDeleteTriggers(EState *fc_estate, ResultRelInfo *fc_relinfo,
					 HeapTuple fc_trigtuple)
{
	TriggerDesc *fc_trigdesc = fc_relinfo->ri_TrigDesc;
	TupleTableSlot *fc_slot = ExecGetTriggerOldSlot(fc_estate, fc_relinfo);
	TriggerData fc_LocTriggerData = {0};
	int			fc_i;

	fc_LocTriggerData.type = T_TriggerData;
	fc_LocTriggerData.tg_event = TRIGGER_EVENT_DELETE |
		TRIGGER_EVENT_ROW |
		TRIGGER_EVENT_INSTEAD;
	fc_LocTriggerData.tg_relation = fc_relinfo->ri_RelationDesc;

	ExecForceStoreHeapTuple(fc_trigtuple, fc_slot, false);

	for (fc_i = 0; fc_i < fc_trigdesc->numtriggers; fc_i++)
	{
		HeapTuple	fc_rettuple;
		Trigger    *fc_trigger = &fc_trigdesc->triggers[fc_i];

		if (!TRIGGER_TYPE_MATCHES(fc_trigger->tgtype,
								  TRIGGER_TYPE_ROW,
								  TRIGGER_TYPE_INSTEAD,
								  TRIGGER_TYPE_DELETE))
			continue;
		if (!fc_TriggerEnabled(fc_estate, fc_relinfo, fc_trigger, fc_LocTriggerData.tg_event,
							NULL, fc_slot, NULL))
			continue;

		fc_LocTriggerData.tg_trigslot = fc_slot;
		fc_LocTriggerData.tg_trigtuple = fc_trigtuple;
		fc_LocTriggerData.tg_trigger = fc_trigger;
		fc_rettuple = fc_ExecCallTriggerFunc(&fc_LocTriggerData,
									   fc_i,
									   fc_relinfo->ri_TrigFunctions,
									   fc_relinfo->ri_TrigInstrument,
									   GetPerTupleMemoryContext(fc_estate));
		if (fc_rettuple == NULL)
			return false;		/* 删除被抑制 */
		if (fc_rettuple != fc_trigtuple)
			heap_freetuple(fc_rettuple);
	}
	return true;
}

void ExecBSUpdateTriggers(EState *fc_estate, ResultRelInfo *fc_relinfo)
{
	TriggerDesc *fc_trigdesc;
	int			fc_i;
	TriggerData fc_LocTriggerData = {0};
	Bitmapset  *fc_updatedCols;

	fc_trigdesc = fc_relinfo->ri_TrigDesc;

	if (fc_trigdesc == NULL)
		return;
	if (!fc_trigdesc->trig_update_before_statement)
		return;

	/* 如果我们在此上下文中已经触发了 BS 触发器则无操作 */
	if (fc_before_stmt_triggers_fired(RelationGetRelid(fc_relinfo->ri_RelationDesc),
								   CMD_UPDATE))
		return;

	/* 语句级触发器作用于父表 */
	Assert(fc_relinfo->ri_RootResultRelInfo == NULL);

	fc_updatedCols = ExecGetAllUpdatedCols(fc_relinfo, fc_estate);

	fc_LocTriggerData.type = T_TriggerData;
	fc_LocTriggerData.tg_event = TRIGGER_EVENT_UPDATE |
		TRIGGER_EVENT_BEFORE;
	fc_LocTriggerData.tg_relation = fc_relinfo->ri_RelationDesc;
	fc_LocTriggerData.tg_updatedcols = fc_updatedCols;
	for (fc_i = 0; fc_i < fc_trigdesc->numtriggers; fc_i++)
	{
		Trigger    *fc_trigger = &fc_trigdesc->triggers[fc_i];
		HeapTuple	fc_newtuple;

		if (!TRIGGER_TYPE_MATCHES(fc_trigger->tgtype,
								  TRIGGER_TYPE_STATEMENT,
								  TRIGGER_TYPE_BEFORE,
								  TRIGGER_TYPE_UPDATE))
			continue;
		if (!fc_TriggerEnabled(fc_estate, fc_relinfo, fc_trigger, fc_LocTriggerData.tg_event,
							fc_updatedCols, NULL, NULL))
			continue;

		fc_LocTriggerData.tg_trigger = fc_trigger;
		fc_newtuple = fc_ExecCallTriggerFunc(&fc_LocTriggerData,
									   fc_i,
									   fc_relinfo->ri_TrigFunctions,
									   fc_relinfo->ri_TrigInstrument,
									   GetPerTupleMemoryContext(fc_estate));

		if (fc_newtuple)
			ereport(ERROR,
					(errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
					 errmsg("BEFORE STATEMENT trigger cannot return a value")));
	}
}

void ExecASUpdateTriggers(EState *fc_estate, ResultRelInfo *fc_relinfo,
					 TransitionCaptureState *fc_transition_capture)
{
	TriggerDesc *fc_trigdesc = fc_relinfo->ri_TrigDesc;

	/* 语句级触发器作用于父表 */
	Assert(fc_relinfo->ri_RootResultRelInfo == NULL);

	if (fc_trigdesc && fc_trigdesc->trig_update_after_statement)
		fc_AfterTriggerSaveEvent(fc_estate, fc_relinfo, NULL, NULL,
							  TRIGGER_EVENT_UPDATE,
							  false, NULL, NULL, NIL,
							  ExecGetAllUpdatedCols(fc_relinfo, fc_estate),
							  fc_transition_capture,
							  false);
}

bool ExecBRUpdateTriggersNew(EState *fc_estate, EPQState *fc_epqstate,
						ResultRelInfo *fc_relinfo,
						ItemPointer fc_tupleid,
						HeapTuple fc_fdw_trigtuple,
						TupleTableSlot *fc_newslot,
						TM_Result *fc_tmresult,
						TM_FailureData *fc_tmfd)
{
	TriggerDesc *fc_trigdesc = fc_relinfo->ri_TrigDesc;
	TupleTableSlot *fc_oldslot = ExecGetTriggerOldSlot(fc_estate, fc_relinfo);
	HeapTuple	fc_newtuple = NULL;
	HeapTuple	fc_trigtuple;
	bool		fc_should_free_trig = false;
	bool		fc_should_free_new = false;
	TriggerData fc_LocTriggerData = {0};
	int			fc_i;
	Bitmapset  *fc_updatedCols;
	LockTupleMode fc_lockmode;

	/* 确定要使用的锁模式 */
	fc_lockmode = ExecUpdateLockMode(fc_estate, fc_relinfo);

	Assert(HeapTupleIsValid(fc_fdw_trigtuple) ^ ItemPointerIsValid(fc_tupleid));
	if (fc_fdw_trigtuple == NULL)
	{
		TupleTableSlot *fc_epqslot_candidate = NULL;

		/* 获取我们计划更新的磁盘上元组的副本 */
		if (!fc_GetTupleForTrigger(fc_estate, fc_epqstate, fc_relinfo, fc_tupleid,
								fc_lockmode, fc_oldslot, &fc_epqslot_candidate,
								fc_tmresult, fc_tmfd))
			return false;		/* 取消更新操作 */

		/*
		 * 在 READ COMMITTED 隔离级别下，目标元组
		 * 可能因并发更新而更改。在这种情况下，我们有一个原始
		 * 子计划输出元组在 epqslot_candidate 中，需要使用
		 * ExecGetUpdateNewTuple 形成一个新的可插入元组，以替换
		 * 我们在 newslot 中接收到的那个。我们和我们的调用者
		 * 不再对传入的元组有兴趣，因此覆盖 newslot
		 * 以使用更新的数据是可以的。
		 */
		if (fc_epqslot_candidate != NULL)
		{
			TupleTableSlot *fc_epqslot_clean;

			fc_epqslot_clean = ExecGetUpdateNewTuple(fc_relinfo, fc_epqslot_candidate,
												  fc_oldslot);

			/*
			 * 通常，调用者的 newslot 也是由
			 * ExecGetUpdateNewTuple 生成的，因此 epqslot_clean 将是相同
			 * 的槽，不需要复制。但是如果不是，还是要做正确的事情。
			 */
			if (unlikely(fc_newslot != fc_epqslot_clean))
				ExecCopySlot(fc_newslot, fc_epqslot_clean);

			/*
			 * 此时 newslot 包含一个虚拟元组，可能
			 * 引用 oldslot 元组在某些磁盘缓冲区中的某些字段。
			 * 如果该元组位于与原始目标元组不同的页面中，
			 * 那么我们对该缓冲区的唯一固定是 oldslot 的，并且我们
			 * 即将释放它。因此，我们最好将 newslot 物化，以
			 * 确保它不包含对未固定缓冲区的引用。
			 * （我们无论如何都会在下面物化它，但为时已晚，出于安全考虑。）
			 */
			ExecMaterializeSlot(fc_newslot);
		}

		/*
		 * 在这里，我们将 oldslot 转换为一个持有 trigtuple 的物化槽。
		 * 传递给触发器的两个槽都不会持有任何缓冲区固定。
		 */
		fc_trigtuple = ExecFetchSlotHeapTuple(fc_oldslot, true, &fc_should_free_trig);
	}
	else
	{
		/* 将 FDW 提供的元组放入 oldslot 以统一情况 */
		ExecForceStoreHeapTuple(fc_fdw_trigtuple, fc_oldslot, false);
		fc_trigtuple = fc_fdw_trigtuple;
	}

	fc_LocTriggerData.type = T_TriggerData;
	fc_LocTriggerData.tg_event = TRIGGER_EVENT_UPDATE |
		TRIGGER_EVENT_ROW |
		TRIGGER_EVENT_BEFORE;
	fc_LocTriggerData.tg_relation = fc_relinfo->ri_RelationDesc;
	fc_updatedCols = ExecGetAllUpdatedCols(fc_relinfo, fc_estate);
	fc_LocTriggerData.tg_updatedcols = fc_updatedCols;
	for (fc_i = 0; fc_i < fc_trigdesc->numtriggers; fc_i++)
	{
		Trigger    *fc_trigger = &fc_trigdesc->triggers[fc_i];
		HeapTuple	fc_oldtuple;

		if (!TRIGGER_TYPE_MATCHES(fc_trigger->tgtype,
								  TRIGGER_TYPE_ROW,
								  TRIGGER_TYPE_BEFORE,
								  TRIGGER_TYPE_UPDATE))
			continue;
		if (!fc_TriggerEnabled(fc_estate, fc_relinfo, fc_trigger, fc_LocTriggerData.tg_event,
							fc_updatedCols, fc_oldslot, fc_newslot))
			continue;

		if (!fc_newtuple)
			fc_newtuple = ExecFetchSlotHeapTuple(fc_newslot, true, &fc_should_free_new);

		fc_LocTriggerData.tg_trigslot = fc_oldslot;
		fc_LocTriggerData.tg_trigtuple = fc_trigtuple;
		fc_LocTriggerData.tg_newtuple = fc_oldtuple = fc_newtuple;
		fc_LocTriggerData.tg_newslot = fc_newslot;
		fc_LocTriggerData.tg_trigger = fc_trigger;
		fc_newtuple = fc_ExecCallTriggerFunc(&fc_LocTriggerData,
									   fc_i,
									   fc_relinfo->ri_TrigFunctions,
									   fc_relinfo->ri_TrigInstrument,
									   GetPerTupleMemoryContext(fc_estate));

		if (fc_newtuple == NULL)
		{
			if (fc_should_free_trig)
				heap_freetuple(fc_trigtuple);
			if (fc_should_free_new)
				heap_freetuple(fc_oldtuple);
			return false;		/* "不做任何操作" */
		}
		else if (fc_newtuple != fc_oldtuple)
		{
			ExecForceStoreHeapTuple(fc_newtuple, fc_newslot, false);

			/*
			 * 如果触发器返回的元组 / 被存储的是旧
			 * 行版本，并且传递给触发器的堆元组是
			 * 本地分配的，则物化该槽。否则，我们可能
			 * 在仍由槽引用的情况下释放它。
			 */
			if (fc_should_free_trig && fc_newtuple == fc_trigtuple)
				ExecMaterializeSlot(fc_newslot);

			if (fc_should_free_new)
				heap_freetuple(fc_oldtuple);

			/* 如果使用，则信号元组应重新获取 */
			fc_newtuple = NULL;
		}
	}
	if (fc_should_free_trig)
		heap_freetuple(fc_trigtuple);

	return true;
}

/*
 * ABI 兼容的包装器，用于模拟上述函数的旧版本。
 * 请勿在新代码中调用此版本。
 */
bool ExecBRUpdateTriggers(EState *fc_estate, EPQState *fc_epqstate,
					 ResultRelInfo *fc_relinfo,
					 ItemPointer fc_tupleid,
					 HeapTuple fc_fdw_trigtuple,
					 TupleTableSlot *fc_newslot,
					 TM_FailureData *fc_tmfd)
{
	return ExecBRUpdateTriggersNew(fc_estate, fc_epqstate, fc_relinfo, fc_tupleid,
								   fc_fdw_trigtuple, fc_newslot, NULL, fc_tmfd);
}

/*
 * 注意：'src_partinfo' 和 'dst_partinfo'，当非空时，分别指代查询中提到的根分区表的源和目标分区，由'relinfo'给出。
 * 在这种情况下，'tupleid'指的是源分区中“旧”元组的ctid，'newslot'包含目标分区中的“新”元组。
 * 该接口允许支持ExecCrossPartitionUpdateForeignKey()的需求；在这种情况下，is_crosspart_update必须为真。
 */
void ExecARUpdateTriggers(EState *fc_estate, ResultRelInfo *fc_relinfo,
					 ResultRelInfo *fc_src_partinfo,
					 ResultRelInfo *fc_dst_partinfo,
					 ItemPointer fc_tupleid,
					 HeapTuple fc_fdw_trigtuple,
					 TupleTableSlot *fc_newslot,
					 List *fc_recheckIndexes,
					 TransitionCaptureState *fc_transition_capture,
					 bool fc_is_crosspart_update)
{
	TriggerDesc *fc_trigdesc = fc_relinfo->ri_TrigDesc;

	if ((fc_trigdesc && fc_trigdesc->trig_update_after_row) ||
		(fc_transition_capture &&
		 (fc_transition_capture->tcs_update_old_table ||
		  fc_transition_capture->tcs_update_new_table)))
	{
		/*
		 * 注意：如果UPDATE被转换为DELETE+INSERT作为更新分区键操作的一部分，
		 * 则此函数也会分别被调用以用于DELETE和INSERT，以捕捉过渡表行。
		 * 在这种情况下，旧元组或新元组都可以为NULL。
		 */
		TupleTableSlot *fc_oldslot;
		ResultRelInfo *fc_tupsrc;

		Assert((fc_src_partinfo != NULL && fc_dst_partinfo != NULL) ||
			   !fc_is_crosspart_update);

		fc_tupsrc = fc_src_partinfo ? fc_src_partinfo : fc_relinfo;
		fc_oldslot = ExecGetTriggerOldSlot(fc_estate, fc_tupsrc);

		if (fc_fdw_trigtuple == NULL && ItemPointerIsValid(fc_tupleid))
			fc_GetTupleForTrigger(fc_estate,
							   NULL,
							   fc_tupsrc,
							   fc_tupleid,
							   LockTupleExclusive,
							   fc_oldslot,
							   NULL,
							   NULL,
							   NULL);
		else if (fc_fdw_trigtuple != NULL)
			ExecForceStoreHeapTuple(fc_fdw_trigtuple, fc_oldslot, false);
		else
			ExecClearTuple(fc_oldslot);

		fc_AfterTriggerSaveEvent(fc_estate, fc_relinfo,
							  fc_src_partinfo, fc_dst_partinfo,
							  TRIGGER_EVENT_UPDATE,
							  true,
							  fc_oldslot, fc_newslot, fc_recheckIndexes,
							  ExecGetAllUpdatedCols(fc_relinfo, fc_estate),
							  fc_transition_capture,
							  fc_is_crosspart_update);
	}
}

bool ExecIRUpdateTriggers(EState *fc_estate, ResultRelInfo *fc_relinfo,
					 HeapTuple fc_trigtuple, TupleTableSlot *fc_newslot)
{
	TriggerDesc *fc_trigdesc = fc_relinfo->ri_TrigDesc;
	TupleTableSlot *fc_oldslot = ExecGetTriggerOldSlot(fc_estate, fc_relinfo);
	HeapTuple	fc_newtuple = NULL;
	bool		fc_should_free;
	TriggerData fc_LocTriggerData = {0};
	int			fc_i;

	fc_LocTriggerData.type = T_TriggerData;
	fc_LocTriggerData.tg_event = TRIGGER_EVENT_UPDATE |
		TRIGGER_EVENT_ROW |
		TRIGGER_EVENT_INSTEAD;
	fc_LocTriggerData.tg_relation = fc_relinfo->ri_RelationDesc;

	ExecForceStoreHeapTuple(fc_trigtuple, fc_oldslot, false);

	for (fc_i = 0; fc_i < fc_trigdesc->numtriggers; fc_i++)
	{
		Trigger    *fc_trigger = &fc_trigdesc->triggers[fc_i];
		HeapTuple	fc_oldtuple;

		if (!TRIGGER_TYPE_MATCHES(fc_trigger->tgtype,
								  TRIGGER_TYPE_ROW,
								  TRIGGER_TYPE_INSTEAD,
								  TRIGGER_TYPE_UPDATE))
			continue;
		if (!fc_TriggerEnabled(fc_estate, fc_relinfo, fc_trigger, fc_LocTriggerData.tg_event,
							NULL, fc_oldslot, fc_newslot))
			continue;

		if (!fc_newtuple)
			fc_newtuple = ExecFetchSlotHeapTuple(fc_newslot, true, &fc_should_free);

		fc_LocTriggerData.tg_trigslot = fc_oldslot;
		fc_LocTriggerData.tg_trigtuple = fc_trigtuple;
		fc_LocTriggerData.tg_newslot = fc_newslot;
		fc_LocTriggerData.tg_newtuple = fc_oldtuple = fc_newtuple;

		fc_LocTriggerData.tg_trigger = fc_trigger;
		fc_newtuple = fc_ExecCallTriggerFunc(&fc_LocTriggerData,
									   fc_i,
									   fc_relinfo->ri_TrigFunctions,
									   fc_relinfo->ri_TrigInstrument,
									   GetPerTupleMemoryContext(fc_estate));
		if (fc_newtuple == NULL)
		{
			return false;		/* "不做任何操作" */
		}
		else if (fc_newtuple != fc_oldtuple)
		{
			ExecForceStoreHeapTuple(fc_newtuple, fc_newslot, false);

			if (fc_should_free)
				heap_freetuple(fc_oldtuple);

			/* 如果使用，则信号元组应重新获取 */
			fc_newtuple = NULL;
		}
	}

	return true;
}

void ExecBSTruncateTriggers(EState *fc_estate, ResultRelInfo *fc_relinfo)
{
	TriggerDesc *fc_trigdesc;
	int			fc_i;
	TriggerData fc_LocTriggerData = {0};

	fc_trigdesc = fc_relinfo->ri_TrigDesc;

	if (fc_trigdesc == NULL)
		return;
	if (!fc_trigdesc->trig_truncate_before_statement)
		return;

	fc_LocTriggerData.type = T_TriggerData;
	fc_LocTriggerData.tg_event = TRIGGER_EVENT_TRUNCATE |
		TRIGGER_EVENT_BEFORE;
	fc_LocTriggerData.tg_relation = fc_relinfo->ri_RelationDesc;

	for (fc_i = 0; fc_i < fc_trigdesc->numtriggers; fc_i++)
	{
		Trigger    *fc_trigger = &fc_trigdesc->triggers[fc_i];
		HeapTuple	fc_newtuple;

		if (!TRIGGER_TYPE_MATCHES(fc_trigger->tgtype,
								  TRIGGER_TYPE_STATEMENT,
								  TRIGGER_TYPE_BEFORE,
								  TRIGGER_TYPE_TRUNCATE))
			continue;
		if (!fc_TriggerEnabled(fc_estate, fc_relinfo, fc_trigger, fc_LocTriggerData.tg_event,
							NULL, NULL, NULL))
			continue;

		fc_LocTriggerData.tg_trigger = fc_trigger;
		fc_newtuple = fc_ExecCallTriggerFunc(&fc_LocTriggerData,
									   fc_i,
									   fc_relinfo->ri_TrigFunctions,
									   fc_relinfo->ri_TrigInstrument,
									   GetPerTupleMemoryContext(fc_estate));

		if (fc_newtuple)
			ereport(ERROR,
					(errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
					 errmsg("BEFORE STATEMENT trigger cannot return a value")));
	}
}

void ExecASTruncateTriggers(EState *fc_estate, ResultRelInfo *fc_relinfo)
{
	TriggerDesc *fc_trigdesc = fc_relinfo->ri_TrigDesc;

	if (fc_trigdesc && fc_trigdesc->trig_truncate_after_statement)
		fc_AfterTriggerSaveEvent(fc_estate, fc_relinfo,
							  NULL, NULL,
							  TRIGGER_EVENT_TRUNCATE,
							  false, NULL, NULL, NIL, NULL, NULL,
							  false);
}


/*
 * 将元组获取到"oldslot"，必要时处理锁定和EPQ
 */
static bool fc_GetTupleForTrigger(EState *fc_estate,
				   EPQState *fc_epqstate,
				   ResultRelInfo *fc_relinfo,
				   ItemPointer fc_tid,
				   LockTupleMode fc_lockmode,
				   TupleTableSlot *fc_oldslot,
				   TupleTableSlot **fc_epqslot,
				   TM_Result *fc_tmresultp,
				   TM_FailureData *fc_tmfdp)
{
	Relation	fc_relation = fc_relinfo->ri_RelationDesc;

	if (fc_epqslot != NULL)
	{
		TM_Result	fc_test;
		TM_FailureData fc_tmfd;
		int			fc_lockflags = 0;

		*fc_epqslot = NULL;

		/* 调用者必须传递一个epqstate，如果可能的话EvalPlanQual */
		Assert(fc_epqstate != NULL);

		/*
		 * 锁定元组以便更新
		 */
		if (!IsolationUsesXactSnapshot())
			fc_lockflags |= TUPLE_LOCK_FLAG_FIND_LAST_VERSION;
		fc_test = table_tuple_lock(fc_relation, fc_tid, fc_estate->es_snapshot, fc_oldslot,
								fc_estate->es_output_cid,
								fc_lockmode, LockWaitBlock,
								fc_lockflags,
								&fc_tmfd);

		/* 让调用者知道该操作的状态 */
		if (fc_tmresultp)
			*fc_tmresultp = fc_test;
		if (fc_tmfdp)
			*fc_tmfdp = fc_tmfd;

		switch (fc_test)
		{
			case TM_SelfModified:

				/*
				 * 目标元组已被当前命令或当前事务中的后续命令更新或删除。
				 * 在前一种情况下，我们忽略该元组，在后一种情况下抛出错误，
				 * 原因与nodeModifyTable.c中的ExecUpdate和ExecDelete中列出的相同。
				 */
				if (fc_tmfd.cmax != fc_estate->es_output_cid)
					ereport(ERROR,
							(errcode(ERRCODE_TRIGGERED_DATA_CHANGE_VIOLATION),
							 errmsg("tuple to be updated was already modified by an operation triggered by the current command"),
							 errhint("Consider using an AFTER trigger instead of a BEFORE trigger to propagate changes to other rows.")));

				/* 将其视为已删除；不处理 */
				return false;

			case TM_Ok:
				if (fc_tmfd.traversed)
				{
					/*
					 * 使用EPQ重新检查元组。对于MERGE，我们将此留给调用者（它必须进行额外的重新检查，可能最终执行完全不同的操作）。
					 */
					if (fc_estate->es_plannedstmt->commandType == CMD_MERGE)
					{
						if (fc_tmresultp)
							*fc_tmresultp = TM_Updated;
						return false;
					}

					*fc_epqslot = EvalPlanQual(fc_epqstate,
											fc_relation,
											fc_relinfo->ri_RangeTableIndex,
											fc_oldslot);

					/*
					 * 如果更新元组的PlanQual失败 - 我们必须不处理此元组！
					 */
					if (TupIsNull(*fc_epqslot))
					{
						*fc_epqslot = NULL;
						return false;
					}
				}
				break;

			case TM_Updated:
				if (IsolationUsesXactSnapshot())
					ereport(ERROR,
							(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
							 errmsg("could not serialize access due to concurrent update")));
				elog(ERROR, "unexpected table_tuple_lock status: %u", fc_test);
				break;

			case TM_Deleted:
				if (IsolationUsesXactSnapshot())
					ereport(ERROR,
							(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
							 errmsg("could not serialize access due to concurrent delete")));
				/* 元组已被删除 */
				return false;

			case TM_Invisible:
				elog(ERROR, "attempted to lock invisible tuple");
				break;

			default:
				elog(ERROR, "unrecognized table_tuple_lock status: %u", fc_test);
				return false;	/* 保持编译器安静 */
		}
	}
	else
	{
		/*
		 * 我们期望元组存在，因此只需非常简单的错误处理即可。
		 */
		if (!table_tuple_fetch_row_version(fc_relation, fc_tid, SnapshotAny,
										   fc_oldslot))
			elog(ERROR, "failed to fetch tuple for trigger");
	}

	return true;
}

/*
 * 触发器是否启用？
 */
static bool fc_TriggerEnabled(EState *fc_estate, ResultRelInfo *fc_relinfo,
			   Trigger *fc_trigger, TriggerEvent fc_event,
			   Bitmapset *fc_modifiedCols,
			   TupleTableSlot *fc_oldslot, TupleTableSlot *fc_newslot)
{
	/* 检查复制角色相关的启用状态 */
	if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
	{
		if (fc_trigger->tgenabled == TRIGGER_FIRES_ON_ORIGIN ||
			fc_trigger->tgenabled == TRIGGER_DISABLED)
			return false;
	}
	else						/* ORIGIN或LOCAL角色 */
	{
		if (fc_trigger->tgenabled == TRIGGER_FIRES_ON_REPLICA ||
			fc_trigger->tgenabled == TRIGGER_DISABLED)
			return false;
	}

	/*
	 * 检查特定列的触发器（仅对于UPDATE可能，实际上我们*必须*对其他事件类型忽略tgattr）
	 */
	if (fc_trigger->tgnattr > 0 && TRIGGER_FIRED_BY_UPDATE(fc_event))
	{
		int			fc_i;
		bool		fc_modified;

		fc_modified = false;
		for (fc_i = 0; fc_i < fc_trigger->tgnattr; fc_i++)
		{
			if (bms_is_member(fc_trigger->tgattr[fc_i] - FirstLowInvalidHeapAttributeNumber,
							  fc_modifiedCols))
			{
				fc_modified = true;
				break;
			}
		}
		if (!fc_modified)
			return false;
	}

	/* 检查WHEN子句 */
	if (fc_trigger->tgqual)
	{
		ExprState **fc_predicate;
		ExprContext *fc_econtext;
		MemoryContext fc_oldContext;
		int			fc_i;

		Assert(fc_estate != NULL);

		/*
		 * 触发器是relinfo->ri_TrigDesc->triggers[]的一个元素；找到与relinfo->ri_TrigWhenExprs[]的匹配元素
		 */
		fc_i = fc_trigger - fc_relinfo->ri_TrigDesc->triggers;
		fc_predicate = &fc_relinfo->ri_TrigWhenExprs[fc_i];

		/*
		 * 如果这是此WHEN表达式的第一次遍历，为其构建表达式树节点。
		 * 将它们保存在每个查询的内存上下文中，以便在查询期间存活。
		 */
		if (*fc_predicate == NULL)
		{
			Node	   *fc_tgqual;

			fc_oldContext = MemoryContextSwitchTo(fc_estate->es_query_cxt);
			fc_tgqual = stringToNode(fc_trigger->tgqual);
			/* 将对OLD和NEW的引用更改为INNER_VAR和OUTER_VAR */
			ChangeVarNodes(fc_tgqual, PRS2_OLD_VARNO, INNER_VAR, 0);
			ChangeVarNodes(fc_tgqual, PRS2_NEW_VARNO, OUTER_VAR, 0);
			/* ExecPrepareQual希望隐式-AND形式 */
			fc_tgqual = (Node *) make_ands_implicit((Expr *) fc_tgqual);
			*fc_predicate = ExecPrepareQual((List *) fc_tgqual, fc_estate);
			MemoryContextSwitchTo(fc_oldContext);
		}

		/*
		 * 我们将使用EState的每个元组上下文来评估WHEN表达式（如果尚不存在则创建它）。
		 */
		fc_econtext = GetPerTupleExprContext(fc_estate);

		/*
		 * 最后评估表达式，使旧的和/或新的元组分别作为 INNER_VAR/OUTER_VAR 可用。
		 */
		fc_econtext->ecxt_innertuple = fc_oldslot;
		fc_econtext->ecxt_outertuple = fc_newslot;
		if (!ExecQual(*fc_predicate, fc_econtext))
			return false;
	}

	return true;
}


/* ----------
 * 后触发器相关内容
 *
 * AfterTriggersData 结构体保存当前事务树中挂起的 AFTER 触发事件的数据。
 * （BEFORE 触发器立即被触发，所以我们不需要任何持久状态来保存它们。）该结构体
 * 及其大部分附属数据保存在 TopTransactionContext 中；然而，一些可以更早丢弃的数据
 * 出现在相关子事务的 CurTransactionContext 中。此外，各个事件记录
 * 保存在 TopTransactionContext 的一个单独子上下文中。这主要是为了便于从内存上下文
 * 转储中了解触发事件占用了多少空间。
 *
 * 由于挂起事件列表可能会变得很大，我们花费相当大的努力来最小化每个事件的内存消耗。
 * 事件记录被分组为块，而相同块中类似事件的公共数据仅存储一次。
 *
 * XXX 如果每事件数据变得太大，我们需要能够将其保存到文件中。
 * ----------
 */

/* 每个触发器的 SET CONSTRAINT 状态 */
typedef struct SetConstraintTriggerData
{
	Oid			sct_tgoid;
	bool		sct_tgisdeferred;
} SetConstraintTriggerData;

typedef struct SetConstraintTriggerData *SetConstraintTrigger;

/*
 * SET CONSTRAINT 事务内状态。
 *
 * 我们将其作为一个单独的 palloc'd 对象，以便可以轻松复制和释放。
 *
 * all_isset 和 all_isdeferred 用于跟踪
 * SET CONSTRAINTS ALL {DEFERRED, IMMEDIATE}。
 *
 * trigstates[] 存储每个触发器的 tgisdeferred 设置。
 */
typedef struct SetConstraintStateData
{
	bool		all_isset;
	bool		all_isdeferred;
	int			numstates;		/* 正在使用的 trigstates[] 条目的数量 */
	int			numalloc;		/* trigstates[] 的分配大小 */
	SetConstraintTriggerData trigstates[FLEXIBLE_ARRAY_MEMBER];
} SetConstraintStateData;

typedef SetConstraintStateData *SetConstraintState;


/*
 * 每个触发事件的数据
 *
 * 实际的每个事件数据 AfterTriggerEventData 包括 DONE/IN_PROGRESS
 * 状态位，最多两个元组 CTIDs，以及可选的两个分区 OIDs。
 * 每个事件记录还关联一个 AfterTriggerSharedData，该数据在
 * “块”内的所有相似事件实例中共享。
 *
 * 对于行级触发器，我们安排不浪费存储在不需要的 ctid
 * 字段上。常规表的更新使用两个；常规表的插入和删除使用一个；
 * 外部表总是使用零，并将元组保存到一个元组存储中。
 * AFTER_TRIGGER_FDW_FETCH 指示 AfterTriggerExecute() 从该
 * tuplestore 中检索一个新的元组或一对元组，而
 * AFTER_TRIGGER_FDW_REUSE 指示它使用最近检索到的
 * 元组。这允许在外部表上的行级触发器数量不论多少时
 * 只存储一次元组。
 *
 * 当对分区表的更新导致行在分区之间移动时，
 * 两个分区的 OIDs 也会被存储，以便可以获取元组；
 * 这样的条目标记为 AFTER_TRIGGER_CP_UPDATE（“跨分区更新”）。
 *
 * 请注意，我们需要在外部表上以准确的顺序触发触发器，
 * 以便元组可以按正确的顺序从 tuplestore 中取出。
 * 为确保这一点，我们禁止在外部表上使用可延迟的（约束）触发器。
 * 这也确保这些触发器不会被延迟到外部触发查询层，
 * 这意味着在查询层结束时销毁 tuplestore 是可以的。
 *
 * 语句级触发器总是带有 AFTER_TRIGGER_1CTID，尽管它们
 * 不需要 ctid 字段。我们缺乏标志位空间来整齐地表示该
 * 独特的情况，而这似乎并不值得太多麻烦。
 *
 * 注意：ats_firing_id 最初为零，当设置 AFTER_TRIGGER_IN_PROGRESS
 * 时会被设置为其他值。它表示触发器将在其中被触发的
 * 触发周期（或已触发，如果设置了 DONE）。
 * 尽管这是可变状态，我们可以将其保留在 AfterTriggerSharedData
 * 中，因为在给定事件列表中同类型事件的所有实例将
 * 同时被触发，如果它们在相同的触发周期之间排队。
 * 所以我们只需要确保在将新事件附加到现有 AfterTriggerSharedData
 * 记录时 ats_firing_id 为零。
 */
typedef uint32 TriggerFlags;

#define AFTER_TRIGGER_OFFSET			0x07FFFFFF	/* 必须是低位 */
#define AFTER_TRIGGER_DONE				0x80000000
#define AFTER_TRIGGER_IN_PROGRESS		0x40000000
/* 描述此事件的大小和元组来源的位 */
#define AFTER_TRIGGER_FDW_REUSE			0x00000000
#define AFTER_TRIGGER_FDW_FETCH			0x20000000
#define AFTER_TRIGGER_1CTID				0x10000000
#define AFTER_TRIGGER_2CTID				0x30000000
#define AFTER_TRIGGER_CP_UPDATE			0x08000000
#define AFTER_TRIGGER_TUP_BITS			0x38000000
typedef struct AfterTriggerSharedData *AfterTriggerShared;

typedef struct AfterTriggerSharedData
{
	TriggerEvent ats_event;		/* 事件类型指示器，见 trigger.h */
	Oid			ats_tgoid;		/* 触发器的 ID */
	Oid			ats_relid;		/* 它所针对的关系 */
	CommandId	ats_firing_id;	/* 触发周期的 ID */
	struct AfterTriggersTableData *ats_table;	/* 转换表访问 */
	Bitmapset  *ats_modifiedcols;	/* 修改的列 */
} AfterTriggerSharedData;

typedef struct AfterTriggerEventData *AfterTriggerEvent;

typedef struct AfterTriggerEventData
{
	TriggerFlags ate_flags;		/* 状态位和共享数据的偏移量 */
	ItemPointerData ate_ctid1;	/* 插入的、删除的或旧的更新元组 */
	ItemPointerData ate_ctid2;	/* 新的更新元组 */

	/*
	 * 在对分区表进行跨分区更新时，我们还会存储
	 * 源分区和目标分区的OID，以便分别获取
	 * 旧元组(ctid1)和新元组(ctid2)。
	 */
	Oid			ate_src_part;
	Oid			ate_dst_part;
} AfterTriggerEventData;

/* AfterTriggerEventData，减去 ate_src_part，ate_dst_part */
typedef struct AfterTriggerEventDataNoOids
{
	TriggerFlags ate_flags;
	ItemPointerData ate_ctid1;
	ItemPointerData ate_ctid2;
}			AfterTriggerEventDataNoOids;

/* AfterTriggerEventData，减去 ate_*_part 和 ate_ctid2 */
typedef struct AfterTriggerEventDataOneCtid
{
	TriggerFlags ate_flags;		/* 状态位和共享数据的偏移量 */
	ItemPointerData ate_ctid1;	/* 插入的、删除的或旧的更新元组 */
}			AfterTriggerEventDataOneCtid;

/* AfterTriggerEventData，减去 ate_*_part，ate_ctid1 和 ate_ctid2 */
typedef struct AfterTriggerEventDataZeroCtids
{
	TriggerFlags ate_flags;		/* 状态位和共享数据的偏移量 */
}			AfterTriggerEventDataZeroCtids;

#define SizeofTriggerEvent(evt) \
	(((evt)->ate_flags & AFTER_TRIGGER_TUP_BITS) == AFTER_TRIGGER_CP_UPDATE ? \
	 sizeof(AfterTriggerEventData) : \
	 (((evt)->ate_flags & AFTER_TRIGGER_TUP_BITS) == AFTER_TRIGGER_2CTID ? \
	  sizeof(AfterTriggerEventDataNoOids) : \
	  (((evt)->ate_flags & AFTER_TRIGGER_TUP_BITS) == AFTER_TRIGGER_1CTID ? \
	   sizeof(AfterTriggerEventDataOneCtid) : \
	   sizeof(AfterTriggerEventDataZeroCtids))))

#define GetTriggerSharedData(evt) \
	((AfterTriggerShared) ((char *) (evt) + ((evt)->ate_flags & AFTER_TRIGGER_OFFSET)))

/*
 * 为了避免 palloc 额外开销，我们在数组中保持触发器事件，
 * 每次增加较大的块（更复杂的可扩展数组版本）。
 * 在 CHUNK_DATA_START 和 freeptr 之间的空间被 AfterTriggerEventData 
 * 记录占用；在 endfree 和 endptr 之间的空间被 
 * AfterTriggerSharedData 记录占用。
 */
typedef struct AfterTriggerEventChunk
{
	struct AfterTriggerEventChunk *next;	/* 列表链接 */
	char	   *freeptr;		/* 块中空闲空间的开始 */
	char	   *endfree;		/* 块中空闲空间的结束 */
	char	   *endptr;			/* 块的结束 */
	/* 事件数据接下来这里 */
} AfterTriggerEventChunk;

#define CHUNK_DATA_START(cptr) ((char *) (cptr) + MAXALIGN(sizeof(AfterTriggerEventChunk)))

/* 事件列表 */
typedef struct AfterTriggerEventList
{
	AfterTriggerEventChunk *head;
	AfterTriggerEventChunk *tail;
	char	   *tailfree;		/* 尾部块的 freeptr */
} AfterTriggerEventList;

/* 帮助遍历事件列表的宏 */
#define for_each_chunk(cptr, evtlist) \
	for (cptr = (evtlist).head; cptr != NULL; cptr = cptr->next)
#define for_each_event(eptr, cptr) \
	for (eptr = (AfterTriggerEvent) CHUNK_DATA_START(cptr); \
		 (char *) eptr < (cptr)->freeptr; \
		 eptr = (AfterTriggerEvent) (((char *) eptr) + SizeofTriggerEvent(eptr)))
/* 如果不需要特殊的每块处理，请使用此项 */
#define for_each_event_chunk(eptr, cptr, evtlist) \
	for_each_chunk(cptr, evtlist) for_each_event(eptr, cptr)

/* 从可能不是列表开始的起点遍历的宏 */
#define for_each_chunk_from(cptr) \
	for (; cptr != NULL; cptr = cptr->next)
#define for_each_event_from(eptr, cptr) \
	for (; \
		 (char *) eptr < (cptr)->freeptr; \
		 eptr = (AfterTriggerEvent) (((char *) eptr) + SizeofTriggerEvent(eptr)))


/*
 * AFTER TRIGGERS模块的所有每个事务数据。
 *
 * AfterTriggersData包含以下字段：
 *
 * firing_counter在每次调用afterTriggerInvokeEvents时递增。
 * 我们用当前触发周期的ID标记可触发事件，以便我们可以
 * 知道哪些需要处理。这确保了如果触发
 * 函数选择执行SET CONSTRAINTS时的合理行为：内部的SET CONSTRAINTS
 * 只会触发那些尚未计划触发的事件。
 *
 * state跟踪SET CONSTRAINTS的事务局部影响。
 * 这在失败的子事务之间保存和恢复。
 *
 * events是当前延迟事件的列表。这在
 * 当前事务的所有子事务中是全局的。在子事务
 * 中止时，我们知道子事务添加的事件在
 * 列表的末尾，因此相对容易丢弃它们。事件
 * 列表块本身存储在event_cxt中。
 *
 * query_depth是当前嵌套AfterTriggerBeginQuery调用的深度
 * （当堆栈为空时为-1）。
 *
 * query_stack[query_depth]是每个查询级别的数据，包括这些字段：
 *
 * events是当前查询排队的AFTER触发事件列表。
 * 在匹配的AfterTriggerEndQuery调用发生之前，
 * 这些都是无效的。此时我们会触发即时模式触发器，并将任何
 * 延迟事件附加到主事件列表中。
 *
 * fdw_tuplestore是一个包含当前查询排队事件所需的
 * 外部表元组的元组存储。（注意：我们只使用一个
 * 元组存储，即使可能涉及多个外部表。
 * 这样做没有问题，因为元组存储并不关心它们存储的元组内容；
 * 但有可能在某一天会出现问题。）
 *
 * tables是当前查询目标表的AfterTriggersTableData结构体列表
 * （见下文）。
 *
 * maxquerydepth只是query_stack的分配长度。
 *
 * trans_stack保存每个子事务的数据，包括这些字段：
 *
 * state是NULL或指向已保存的SET CONSTRAINTS
 * 状态数据的指针。每个修改状态的子事务级别
 * 首先保存一个副本，我们用来在中止时恢复状态。
 *
 * events是事件头/尾指针的副本，
 * 我们用来在子事务中止时恢复这些值。
 *
 * query_depth是子事务开始时的query_depth值，
 * 我们同样在子事务中止时用来清理。
 *
 * firing_counter是子事务开始时的firing_counter值。
 * 我们用它识别在已中止的子事务中被触发（或标记
 * 为触发）的延迟触发器。
 *
 * 我们使用GetCurrentTransactionNestLevel()来确定在trans_stack
 * 中的正确数组索引。maxtransdepth是trans_stack中分配的条目数量。
 * （通过不保持自己的堆栈指针，我们可以避免在子事务
 * 中止期间的错误导致在同一嵌套深度多次调用
 * AfterTriggerEndSubXact()的问题。）
 *
 * 我们为当前查询的每个目标表，以及每个操作模式（INSERT/UPDATE/DELETE），
 * 创建一个AfterTriggersTableData结构，前提是它具有
 * 转换表或语句级触发器。这用于
 * 持有相关的转换表，及跟踪我们是否已排队语句触发器的信息。
 * （我们使用该信息来防止对同一语句触发器重复触发，
 * 或实际上对每组转换表集合触发一次。）这些结构体，以及转换
 * 表元组存储，存在于（子）事务的CurTransactionContext中。
 * 这样的生命周期是足够的，因为我们不允许
 * 转换表被可延迟触发器使用，因此它们只需存活到
 * AfterTriggerEndQuery。
 */
typedef struct AfterTriggersQueryData AfterTriggersQueryData;
typedef struct AfterTriggersTransData AfterTriggersTransData;
typedef struct AfterTriggersTableData AfterTriggersTableData;

typedef struct AfterTriggersData
{
	CommandId	firing_counter; /* 下一个要分配的触发器 ID */
	SetConstraintState state;	/* 当前活动的 S C 状态 */
	AfterTriggerEventList events;	/* 延迟事件列表 */
	MemoryContext event_cxt;	/* 事件的内存上下文（如果有的话） */

	/* 每个查询级别的数据： */
	AfterTriggersQueryData *query_stack;	/* 下方所示结构体的数组 */
	int			query_depth;	/* 上述数组中的当前索引 */
	int			maxquerydepth;	/* 上述数组的已分配长度 */

	/* 每个子事务级别的数据： */
	AfterTriggersTransData *trans_stack;	/* 下方所示结构体的数组 */
	int			maxtransdepth;	/* 上述数组的已分配长度 */
} AfterTriggersData;

struct AfterTriggersQueryData
{
	AfterTriggerEventList events;	/* 此查询待处理的事件 */
	Tuplestorestate *fdw_tuplestore;	/* 相关事件的外部元组 */
	List	   *tables;			/* AfterTriggersTableData 的列表，见下文 */
};

struct AfterTriggersTransData
{
	/* 这些字段仅用于在子事务回滚时重置： */
	SetConstraintState state;	/* 保存的 S C 状态，或如果尚未保存则为 NULL */
	AfterTriggerEventList events;	/* 保存的列表指针 */
	int			query_depth;	/* 保存的查询深度 */
	CommandId	firing_counter; /* 保存的 firing_counter */
};

struct AfterTriggersTableData
{
	/* relid + cmdType 形成这些结构体的查找键： */
	Oid			relid;			/* 目标表的 OID */
	CmdType		cmdType;		/* 事件类型，CMD_INSERT/UPDATE/DELETE */
	bool		closed;			/* 当不再可以添加元组时为真 */
	bool		before_trig_done;	/* 我们是否已经排队了 BS 触发器？ */
	bool		after_trig_done;	/* 我们是否已经排队了 AS 触发器？ */
	AfterTriggerEventList after_trig_events;	/* 如果是，保存的列表指针 */

	/*
	 * 我们为 UPDATE/INSERT/DELETE 维护单独的过渡表，因为
	 * MERGE 可以在一个语句中执行这三种操作。请注意，UPDATE
	 * 需要旧的和新的过渡表，而 INSERT 仅需要新的，
	 * DELETE 仅需要旧的。
	 */

	/* 用于 UPDATE 的“旧”过渡表（如果有的话） */
	Tuplestorestate *old_upd_tuplestore;
	/* 用于 UPDATE 的“新”过渡表（如果有的话） */
	Tuplestorestate *new_upd_tuplestore;
	/* 用于 DELETE 的“旧”过渡表（如果有的话） */
	Tuplestorestate *old_del_tuplestore;
	/* 用于 INSERT 的“新”过渡表（如果有的话） */
	Tuplestorestate *new_ins_tuplestore;

	TupleTableSlot *storeslot;	/* 用于转换为元组存储格式 */
};

static AfterTriggersData afterTriggers;

static void fc_AfterTriggerExecute(EState *fc_estate,
								AfterTriggerEvent fc_event,
								ResultRelInfo *fc_relInfo,
								ResultRelInfo *fc_src_relInfo,
								ResultRelInfo *fc_dst_relInfo,
								TriggerDesc *fc_trigdesc,
								FmgrInfo *fc_finfo,
								Instrumentation *fc_instr,
								MemoryContext fc_per_tuple_context,
								TupleTableSlot *fc_trig_tuple_slot1,
								TupleTableSlot *fc_trig_tuple_slot2);
static AfterTriggersTableData *fc_GetAfterTriggersTableData(Oid fc_relid,
														 CmdType fc_cmdType);
static TupleTableSlot *fc_GetAfterTriggersStoreSlot(AfterTriggersTableData *fc_table,
												 TupleDesc fc_tupdesc);
static Tuplestorestate *fc_GetAfterTriggersTransitionTable(int fc_event,
														TupleTableSlot *fc_oldslot,
														TupleTableSlot *fc_newslot,
														TransitionCaptureState *fc_transition_capture);
static void fc_TransitionTableAddTuple(EState *fc_estate,
									TransitionCaptureState *fc_transition_capture,
									ResultRelInfo *fc_relinfo,
									TupleTableSlot *fc_slot,
									TupleTableSlot *fc_original_insert_tuple,
									Tuplestorestate *fc_tuplestore);
static void fc_AfterTriggerFreeQuery(AfterTriggersQueryData *fc_qs);
static SetConstraintState fc_SetConstraintStateCreate(int fc_numalloc);
static SetConstraintState fc_SetConstraintStateCopy(SetConstraintState fc_state);
static SetConstraintState fc_SetConstraintStateAddItem(SetConstraintState fc_state,
													Oid fc_tgoid, bool fc_tgisdeferred);
static void fc_cancel_prior_stmt_triggers(Oid fc_relid, CmdType fc_cmdType, int fc_tgevent);


/*
 * 获取当前触发器查询级别的 FDW 元组存储，如果必要则创建它。
 */
static Tuplestorestate * fc_GetCurrentFDWTuplestore(void)
{
	Tuplestorestate *fc_ret;

	fc_ret = afterTriggers.query_stack[afterTriggers.query_depth].fdw_tuplestore;
	if (fc_ret == NULL)
	{
		MemoryContext fc_oldcxt;
		ResourceOwner fc_saveResourceOwner;

		/*
		 * 使元组存储在子事务结束之前有效。我们实际上
		 * 只需要它直到 AfterTriggerEndQuery()。
		 */
		fc_oldcxt = MemoryContextSwitchTo(CurTransactionContext);
		fc_saveResourceOwner = CurrentResourceOwner;
		CurrentResourceOwner = CurTransactionResourceOwner;

		fc_ret = tuplestore_begin_heap(false, false, work_mem);

		CurrentResourceOwner = fc_saveResourceOwner;
		MemoryContextSwitchTo(fc_oldcxt);

		afterTriggers.query_stack[afterTriggers.query_depth].fdw_tuplestore = fc_ret;
	}

	return fc_ret;
}

/* ----------
 * afterTriggerCheckState()
 *
 *	如果触发事件实际上处于 DEFERRED 状态则返回 true。
 * ----------
 */
static bool fc_afterTriggerCheckState(AfterTriggerShared fc_evtshared)
{
	Oid			fc_tgoid = fc_evtshared->ats_tgoid;
	SetConstraintState fc_state = afterTriggers.state;
	int			fc_i;

	/*
	 * 对于不可延迟的触发器（即普通的 AFTER ROW 触发器和
	 * 声明为 NOT DEFERRABLE 的约束），状态始终为 false。
	 */
	if ((fc_evtshared->ats_event & AFTER_TRIGGER_DEFERRABLE) == 0)
		return false;

	/*
	 * 如果存在约束状态，则可能已为此触发器或所有触发器执行
	 * SET CONSTRAINTS。
	 */
	if (fc_state != NULL)
	{
		/* 检查此特定触发器的 SET CONSTRAINTS。 */
		for (fc_i = 0; fc_i < fc_state->numstates; fc_i++)
		{
			if (fc_state->trigstates[fc_i].sct_tgoid == fc_tgoid)
				return fc_state->trigstates[fc_i].sct_tgisdeferred;
		}

		/* 检查 SET CONSTRAINTS ALL。 */
		if (fc_state->all_isset)
			return fc_state->all_isdeferred;
	}

	/*
	 * 否则返回触发器的默认状态。
	 */
	return ((fc_evtshared->ats_event & AFTER_TRIGGER_INITDEFERRED) != 0);
}

/* ----------
 * afterTriggerCopyBitmap()
 *
 * 将位图复制到 AfterTriggerEvents 内存上下文，这是存放后触发器事件的地方。
 * ----------
 */
static Bitmapset * fc_afterTriggerCopyBitmap(Bitmapset *fc_src)
{
	Bitmapset	   *fc_dst;
	MemoryContext	fc_oldcxt;

	if (fc_src == NULL)
		return NULL;

	fc_oldcxt = MemoryContextSwitchTo(afterTriggers.event_cxt);

	fc_dst = bms_copy(fc_src);

	MemoryContextSwitchTo(fc_oldcxt);

	return fc_dst;
}

/* ----------
 * afterTriggerAddEvent()
 *
 * 将一个新的触发器事件添加到指定的队列中。
 * 传入的事件数据会被复制。
 * ----------
 */
static void fc_afterTriggerAddEvent(AfterTriggerEventList *fc_events,
					 AfterTriggerEvent fc_event, AfterTriggerShared fc_evtshared)
{
	Size		fc_eventsize = SizeofTriggerEvent(fc_event);
	Size		fc_needed = fc_eventsize + sizeof(AfterTriggerSharedData);
	AfterTriggerEventChunk *fc_chunk;
	AfterTriggerShared fc_newshared;
	AfterTriggerEvent fc_newevent;

	/*
	 * 如果列表为空或尾块中没有足够的空间，则创建一个新块。
	 * 我们在这里假设总是需要一个新的共享记录。
	 */
	fc_chunk = fc_events->tail;
	if (fc_chunk == NULL ||
		fc_chunk->endfree - fc_chunk->freeptr < fc_needed)
	{
		Size		fc_chunksize;

		/* 如果我们尚未创建事件上下文，则创建一个 */
		if (afterTriggers.event_cxt == NULL)
			afterTriggers.event_cxt =
				AllocSetContextCreate(TopTransactionContext,
									  "AfterTriggerEvents",
									  ALLOCSET_DEFAULT_SIZES);

		/*
		 * 块大小从1KB开始，允许增加到1MB。
		 * 这些数字相当于任意，不过在AFTER_TRIGGER_OFFSET有一个硬限制；
		 * 否则我们就无法通过ate_flags中可用空间将事件记录链接到它们的共享记录。
		 * 另一个约束是，如果块大小变得过大，下面的搜索循环将会变慢，
		 * 这是由于在一个块中存在许多不同事件类型（这种用法模式并不太常见）。
		 * 因此，只有当前一个块中没有太多共享记录时，我们才会将前一个块的大小加倍；
		 * 否则，我们减半。这使我们能够适应当前查询的实际使用模式，
		 * 同时在典型用法中仍然具有较大的块大小。使用的所有块大小应为MAXALIGN的倍数，
		 * 以确保共享记录能够安全对齐。
		 */
#define MIN_CHUNK_SIZE 1024
#define MAX_CHUNK_SIZE (1024*1024)

#if MAX_CHUNK_SIZE > (AFTER_TRIGGER_OFFSET+1)
#error MAX_CHUNK_SIZE must not exceed AFTER_TRIGGER_OFFSET
#endif

		if (fc_chunk == NULL)
			fc_chunksize = MIN_CHUNK_SIZE;
		else
		{
			/* 前一个块大小... */
			fc_chunksize = fc_chunk->endptr - (char *) fc_chunk;
			/* 检查前一个块中的共享记录数量 */
			if ((fc_chunk->endptr - fc_chunk->endfree) <=
				(100 * sizeof(AfterTriggerSharedData)))
				fc_chunksize *= 2; /* 好的，加倍它 */
			else
				fc_chunksize /= 2; /* 共享记录太多 */
			fc_chunksize = Min(fc_chunksize, MAX_CHUNK_SIZE);
		}
		fc_chunk = MemoryContextAlloc(afterTriggers.event_cxt, fc_chunksize);
		fc_chunk->next = NULL;
		fc_chunk->freeptr = CHUNK_DATA_START(fc_chunk);
		fc_chunk->endptr = fc_chunk->endfree = (char *) fc_chunk + fc_chunksize;
		Assert(fc_chunk->endfree - fc_chunk->freeptr >= fc_needed);

		if (fc_events->head == NULL)
			fc_events->head = fc_chunk;
		else
			fc_events->tail->next = fc_chunk;
		fc_events->tail = fc_chunk;
		/* events->tailfree 现在已经不同步，但我们将在下面修复它 */
	}

	/*
	 * 尝试定位已经在块中的匹配共享数据记录。如果没有，创建一个新的。
	 */
	for (fc_newshared = ((AfterTriggerShared) fc_chunk->endptr) - 1;
		 (char *) fc_newshared >= fc_chunk->endfree;
		 fc_newshared--)
	{
		/* 根据字段不同的概率大致比较 */
		if (fc_newshared->ats_tgoid == fc_evtshared->ats_tgoid &&
			fc_newshared->ats_event == fc_evtshared->ats_event &&
			fc_newshared->ats_firing_id == 0 &&
			fc_newshared->ats_table == fc_evtshared->ats_table &&
			fc_newshared->ats_relid == fc_evtshared->ats_relid &&
			bms_equal(fc_newshared->ats_modifiedcols,
					  fc_evtshared->ats_modifiedcols))
			break;
	}
	if ((char *) fc_newshared < fc_chunk->endfree)
	{
		*fc_newshared = *fc_evtshared;
		/* 现在我们必须制作一个合适长寿的位图副本 */
		fc_newshared->ats_modifiedcols = fc_afterTriggerCopyBitmap(fc_evtshared->ats_modifiedcols);
		fc_newshared->ats_firing_id = 0;	/* 只是为了确保 */
		fc_chunk->endfree = (char *) fc_newshared;
	}

	/* 插入数据 */
	fc_newevent = (AfterTriggerEvent) fc_chunk->freeptr;
	memcpy(fc_newevent, fc_event, fc_eventsize);
	/* ... 并将新的事件链接到其共享记录 */
	fc_newevent->ate_flags &= ~AFTER_TRIGGER_OFFSET;
	fc_newevent->ate_flags |= (char *) fc_newshared - (char *) fc_newevent;

	fc_chunk->freeptr += fc_eventsize;
	fc_events->tailfree = fc_chunk->freeptr;
}

/* ----------
 * afterTriggerFreeEventList()
 *
 *	释放给定列表中的所有事件存储。
 * ----------
 */
static void fc_afterTriggerFreeEventList(AfterTriggerEventList *fc_events)
{
	AfterTriggerEventChunk *fc_chunk;

	while ((fc_chunk = fc_events->head) != NULL)
	{
		fc_events->head = fc_chunk->next;
		pfree(fc_chunk);
	}
	fc_events->tail = NULL;
	fc_events->tailfree = NULL;
}

/* ----------
 * afterTriggerRestoreEventList()
 *
 *	将事件列表恢复到其先前的长度，删除自它具有值old_events以来添加的所有事件。
 * ----------
 */
static void fc_afterTriggerRestoreEventList(AfterTriggerEventList *fc_events,
							 const AfterTriggerEventList *fc_old_events)
{
	AfterTriggerEventChunk *fc_chunk;
	AfterTriggerEventChunk *fc_next_chunk;

	if (fc_old_events->tail == NULL)
	{
		/* 恢复到完全空的状态，因此释放所有 */
		fc_afterTriggerFreeEventList(fc_events);
	}
	else
	{
		*fc_events = *fc_old_events;
		/* 释放我们想保留的最后一个块之后的任何块 */
		for (fc_chunk = fc_events->tail->next; fc_chunk != NULL; fc_chunk = fc_next_chunk)
		{
			fc_next_chunk = fc_chunk->next;
			pfree(fc_chunk);
		}
		/* 并清理尾部块以使其长度正确 */
		fc_events->tail->next = NULL;
		fc_events->tail->freeptr = fc_events->tailfree;

		/*
		 * 我们不会努力移除现在未使用的共享数据记录。
		 * 这些记录可能仍然是有用的。
		 */
	}
}

/* ----------
 * afterTriggerDeleteHeadEventChunk()
 *
 * 从查询级别的事件列表中删除第一个事件块。
 * 在查询级别的数据结构的其他地方保持任何事件列表指针同步。
 * ----------
 */
static void fc_afterTriggerDeleteHeadEventChunk(AfterTriggersQueryData *fc_qs)
{
	AfterTriggerEventChunk *fc_target = fc_qs->events.head;
	ListCell   *fc_lc;

	Assert(fc_target && fc_target->next);

	/*
	 * 首先，更新每个表数据中的任何指针，以便它们不会悬挂。
	 * 将过时的指针重置为NULL将使cancel_prior_stmt_triggers从列表头开始，
	 * 这很好。
	 */
	foreach(fc_lc, fc_qs->tables)
	{
		AfterTriggersTableData *fc_table = (AfterTriggersTableData *) lfirst(fc_lc);

		if (fc_table->after_trig_done &&
			fc_table->after_trig_events.tail == fc_target)
		{
			fc_table->after_trig_events.head = NULL;
			fc_table->after_trig_events.tail = NULL;
			fc_table->after_trig_events.tailfree = NULL;
		}
	}

	/* 现在我们可以刷新头块 */
	fc_qs->events.head = fc_target->next;
	pfree(fc_target);
}


/* ----------
 * AfterTriggerExecute()
 *
 *	从堆中获取所需的元组并触发一个
 *	单一的触发函数。
 *
 *	通常，这将在单个关系的触发器中连续多次触发。
 *	因此，我们缓存了打开的关系并在调用者级别提供
 *	fmgr 查找缓存空间。（对于在查询结束时触发的触发器，
 *	我们甚至可以依赖执行器的状态。）
 *
 *	当用于分区表的跨分区更新时，旧的元组通过 'src_relInfo'
 *	（源叶分区）获取，新元组通过 'dst_relInfo'
 *	（目标叶分区）获取，尽管在传递给触发函数之前，
 *	这两者都会转换为根分区表的格式。
 *
 *	event: 当前触发的事件。
 *	relInfo: 事件的结果关系。
 *	src_relInfo: 跨分区更新的源分区
 *	dst_relInfo: 其目标分区
 *	trigdesc: 关系触发信息的工作副本。
 *	finfo: fmgr 查找缓存条目的数组（每个 trigdesc 一个触发器）。
 *	instr: EXPLAIN ANALYZE 仪器节点的数组（每个触发器一个），
 *		如果不需要仪器则为 NULL。
 *	per_tuple_context: 调用触发函数的内存上下文。
 *	trig_tuple_slot1: tg_trigtuple 的临时槽（仅限外部表）
 *	trig_tuple_slot2: tg_newtuple 的临时槽（仅限外部表）
 * ----------
 */
static void fc_AfterTriggerExecute(EState *fc_estate,
					AfterTriggerEvent fc_event,
					ResultRelInfo *fc_relInfo,
					ResultRelInfo *fc_src_relInfo,
					ResultRelInfo *fc_dst_relInfo,
					TriggerDesc *fc_trigdesc,
					FmgrInfo *fc_finfo, Instrumentation *fc_instr,
					MemoryContext fc_per_tuple_context,
					TupleTableSlot *fc_trig_tuple_slot1,
					TupleTableSlot *fc_trig_tuple_slot2)
{
	Relation	fc_rel = fc_relInfo->ri_RelationDesc;
	Relation	fc_src_rel = fc_src_relInfo->ri_RelationDesc;
	Relation	fc_dst_rel = fc_dst_relInfo->ri_RelationDesc;
	AfterTriggerShared fc_evtshared = GetTriggerSharedData(fc_event);
	Oid			fc_tgoid = fc_evtshared->ats_tgoid;
	TriggerData fc_LocTriggerData = {0};
	HeapTuple	fc_rettuple;
	int			fc_tgindx;
	bool		fc_should_free_trig = false;
	bool		fc_should_free_new = false;

	/*
	 * 在 trigdesc 中定位触发器。它可能不存在，实际上，如果
	 * 自事件排队以来触发器被删除，则 trigdesc 可能为 NULL。
	 * 在这种情况下，静默无操作。
	 */
	if (fc_trigdesc == NULL)
		return;
	for (fc_tgindx = 0; fc_tgindx < fc_trigdesc->numtriggers; fc_tgindx++)
	{
		if (fc_trigdesc->triggers[fc_tgindx].tgoid == fc_tgoid)
		{
			fc_LocTriggerData.tg_trigger = &(fc_trigdesc->triggers[fc_tgindx]);
			break;
		}
	}
	if (fc_LocTriggerData.tg_trigger == NULL)
		return;

	/*
	 * 如果正在执行 EXPLAIN ANALYZE，开始将时间计入此触发器。
	 * 我们希望将重新获取元组所花费的时间包括在触发器成本中。
	 */
	if (fc_instr)
		InstrStartNode(fc_instr + fc_tgindx);

	/*
	 * 获取所需的元组。
	 */
	switch (fc_event->ate_flags & AFTER_TRIGGER_TUP_BITS)
	{
		case AFTER_TRIGGER_FDW_FETCH:
			{
				Tuplestorestate *fc_fdw_tuplestore = fc_GetCurrentFDWTuplestore();

				if (!tuplestore_gettupleslot(fc_fdw_tuplestore, true, false,
											 fc_trig_tuple_slot1))
					elog(ERROR, "failed to fetch tuple1 for AFTER trigger");

				if ((fc_evtshared->ats_event & TRIGGER_EVENT_OPMASK) ==
					TRIGGER_EVENT_UPDATE &&
					!tuplestore_gettupleslot(fc_fdw_tuplestore, true, false,
											 fc_trig_tuple_slot2))
					elog(ERROR, "failed to fetch tuple2 for AFTER trigger");
			}
			/* fall through */
		case AFTER_TRIGGER_FDW_REUSE:

			/*
			 * 将元组存储在槽中，以便 tg_trigtuple 不引用
			 * tuplestore 内存。（触发函数正式上有可能排队
			 * 添加到相同 tuplestore 的触发事件，这可能会使其他元组
			 * 超出内存。）这种区分是学术上的，因为我们从一个
			 * 最小的元组开始，该元组以堆元组的形式存储，
			 * 以不同的内存上下文构造，反正也在槽中。
			 */
			fc_LocTriggerData.tg_trigslot = fc_trig_tuple_slot1;
			fc_LocTriggerData.tg_trigtuple =
				ExecFetchSlotHeapTuple(fc_trig_tuple_slot1, true, &fc_should_free_trig);

			if ((fc_evtshared->ats_event & TRIGGER_EVENT_OPMASK) ==
				TRIGGER_EVENT_UPDATE)
			{
				fc_LocTriggerData.tg_newslot = fc_trig_tuple_slot2;
				fc_LocTriggerData.tg_newtuple =
					ExecFetchSlotHeapTuple(fc_trig_tuple_slot2, true, &fc_should_free_new);
			}
			else
			{
				fc_LocTriggerData.tg_newtuple = NULL;
			}
			break;

		default:
			if (ItemPointerIsValid(&(fc_event->ate_ctid1)))
			{
				TupleTableSlot *fc_src_slot = ExecGetTriggerOldSlot(fc_estate,
																 fc_src_relInfo);

				if (!table_tuple_fetch_row_version(fc_src_rel,
												   &(fc_event->ate_ctid1),
												   SnapshotAny,
												   fc_src_slot))
					elog(ERROR, "failed to fetch tuple1 for AFTER trigger");

				/*
				 * 将从源分区中获取的元组存储到
				 * 目标（根分区）表槽中，如有需要则转换。
				 */
				if (fc_src_relInfo != fc_relInfo)
				{
					TupleConversionMap *fc_map = ExecGetChildToRootMap(fc_src_relInfo);

					fc_LocTriggerData.tg_trigslot = ExecGetTriggerOldSlot(fc_estate, fc_relInfo);
					if (fc_map)
					{
						execute_attr_map_slot(fc_map->attrMap,
											  fc_src_slot,
											  fc_LocTriggerData.tg_trigslot);
					}
					else
						ExecCopySlot(fc_LocTriggerData.tg_trigslot, fc_src_slot);
				}
				else
					fc_LocTriggerData.tg_trigslot = fc_src_slot;
				fc_LocTriggerData.tg_trigtuple =
					ExecFetchSlotHeapTuple(fc_LocTriggerData.tg_trigslot, false, &fc_should_free_trig);
			}
			else
			{
				fc_LocTriggerData.tg_trigtuple = NULL;
			}

			/* 如果不在，别碰 ctid2 */
			if (((fc_event->ate_flags & AFTER_TRIGGER_TUP_BITS) == AFTER_TRIGGER_2CTID ||
				 (fc_event->ate_flags & AFTER_TRIGGER_CP_UPDATE)) &&
				ItemPointerIsValid(&(fc_event->ate_ctid2)))
			{
				TupleTableSlot *fc_dst_slot = ExecGetTriggerNewSlot(fc_estate,
																 fc_dst_relInfo);

				if (!table_tuple_fetch_row_version(fc_dst_rel,
												   &(fc_event->ate_ctid2),
												   SnapshotAny,
												   fc_dst_slot))
					elog(ERROR, "failed to fetch tuple2 for AFTER trigger");

				/*
				 * 将从目标分区中获取的元组存储到
				 * 目标（根分区）表槽中，如有需要则转换。
				 */
				if (fc_dst_relInfo != fc_relInfo)
				{
					TupleConversionMap *fc_map = ExecGetChildToRootMap(fc_dst_relInfo);

					fc_LocTriggerData.tg_newslot = ExecGetTriggerNewSlot(fc_estate, fc_relInfo);
					if (fc_map)
					{
						execute_attr_map_slot(fc_map->attrMap,
											  fc_dst_slot,
											  fc_LocTriggerData.tg_newslot);
					}
					else
						ExecCopySlot(fc_LocTriggerData.tg_newslot, fc_dst_slot);
				}
				else
					fc_LocTriggerData.tg_newslot = fc_dst_slot;
				fc_LocTriggerData.tg_newtuple =
					ExecFetchSlotHeapTuple(fc_LocTriggerData.tg_newslot, false, &fc_should_free_new);
			}
			else
			{
				fc_LocTriggerData.tg_newtuple = NULL;
			}
	}

	/*
	 * 设置 tuplestore 信息，以便触发器可以访问
	 * 转换表。当我们第一次让转换表可用于
	 * 触发器时，将其标记为“关闭”，以便不能再更改。如果任何
	 * 同类型的其他事件在当前触发器
	 * 查询级别中排队，它们将进入新的转换表。
	 */
	fc_LocTriggerData.tg_oldtable = fc_LocTriggerData.tg_newtable = NULL;
	if (fc_evtshared->ats_table)
	{
		if (fc_LocTriggerData.tg_trigger->tgoldtable)
		{
			if (TRIGGER_FIRED_BY_UPDATE(fc_evtshared->ats_event))
				fc_LocTriggerData.tg_oldtable = fc_evtshared->ats_table->old_upd_tuplestore;
			else
				fc_LocTriggerData.tg_oldtable = fc_evtshared->ats_table->old_del_tuplestore;
			fc_evtshared->ats_table->closed = true;
		}

		if (fc_LocTriggerData.tg_trigger->tgnewtable)
		{
			if (TRIGGER_FIRED_BY_INSERT(fc_evtshared->ats_event))
				fc_LocTriggerData.tg_newtable = fc_evtshared->ats_table->new_ins_tuplestore;
			else
				fc_LocTriggerData.tg_newtable = fc_evtshared->ats_table->new_upd_tuplestore;
			fc_evtshared->ats_table->closed = true;
		}
	}

	/*
	 * 设置剩余的触发器信息
	 */
	fc_LocTriggerData.type = T_TriggerData;
	fc_LocTriggerData.tg_event =
		fc_evtshared->ats_event & (TRIGGER_EVENT_OPMASK | TRIGGER_EVENT_ROW);
	fc_LocTriggerData.tg_relation = fc_rel;
	if (TRIGGER_FOR_UPDATE(fc_LocTriggerData.tg_trigger->tgtype))
		fc_LocTriggerData.tg_updatedcols = fc_evtshared->ats_modifiedcols;

	MemoryContextReset(fc_per_tuple_context);

	/*
	 * 调用触发器并丢弃可能返回的更新元组。
	 * （不要让 ExecCallTriggerFunc 测量 EXPLAIN 时间。）
	 */
	fc_rettuple = fc_ExecCallTriggerFunc(&fc_LocTriggerData,
								   fc_tgindx,
								   fc_finfo,
								   NULL,
								   fc_per_tuple_context);
	if (fc_rettuple != NULL &&
		fc_rettuple != fc_LocTriggerData.tg_trigtuple &&
		fc_rettuple != fc_LocTriggerData.tg_newtuple)
		heap_freetuple(fc_rettuple);

	/*
	 * 释放资源
	 */
	if (fc_should_free_trig)
		heap_freetuple(fc_LocTriggerData.tg_trigtuple);
	if (fc_should_free_new)
		heap_freetuple(fc_LocTriggerData.tg_newtuple);

	/* 如果是外部表，则不要清除槽的内容 */
	if (fc_trig_tuple_slot1 == NULL)
	{
		if (fc_LocTriggerData.tg_trigslot)
			ExecClearTuple(fc_LocTriggerData.tg_trigslot);
		if (fc_LocTriggerData.tg_newslot)
			ExecClearTuple(fc_LocTriggerData.tg_newslot);
	}

	/*
	 * 如果正在进行 EXPLAIN ANALYZE，停止为此触发器计时，并且计算
	 * 一次“返回的元组”（实际上是触发次数）。
	 */
	if (fc_instr)
		InstrStopNode(fc_instr + fc_tgindx, 1);
}


/*
 * afterTriggerMarkEvents()
 *
 *	扫描给定事件列表，查找尚未调用的事件。标记可以
 *	在当前触发 ID 下调用的事件。
 *
 *	如果 move_list 不为 NULL，则未被调用的事件将
 *	转移到 move_list 中。
 *
 *	当 immediate_only 为 true 时，不调用当前延迟的触发器。
 *	（这只会在主事务退出时为 false。）
 *
 *	如果找到任何可调用的事件，则返回 true。
 */
static bool fc_afterTriggerMarkEvents(AfterTriggerEventList *fc_events,
					   AfterTriggerEventList *fc_move_list,
					   bool fc_immediate_only)
{
	bool		fc_found = false;
	bool		fc_deferred_found = false;
	AfterTriggerEvent fc_event;
	AfterTriggerEventChunk *fc_chunk;

	for_each_event_chunk(fc_event, fc_chunk, *fc_events)
	{
		AfterTriggerShared fc_evtshared = GetTriggerSharedData(fc_event);
		bool		fc_defer_it = false;

		if (!(fc_event->ate_flags &
			  (AFTER_TRIGGER_DONE | AFTER_TRIGGER_IN_PROGRESS)))
		{
			/*
			 * 这个触发器尚未被调用或调度。检查我们是否
			 * 现在应该调用它。
			 */
			if (fc_immediate_only && fc_afterTriggerCheckState(fc_evtshared))
			{
				fc_defer_it = true;
			}
			else
			{
				/*
				 * 将其标记为在此触发周期中触发。
				 */
				fc_evtshared->ats_firing_id = afterTriggers.firing_counter;
				fc_event->ate_flags |= AFTER_TRIGGER_IN_PROGRESS;
				fc_found = true;
			}
		}

		/*
		 * 如果它是延迟的，则将其移至 move_list，如果需要。
		 */
		if (fc_defer_it && fc_move_list != NULL)
		{
			fc_deferred_found = true;
			/* 将其添加到 move_list */
			fc_afterTriggerAddEvent(fc_move_list, fc_event, fc_evtshared);
			/* 标记原始副本为“完成”，以便我们不再执行它 */
			fc_event->ate_flags |= AFTER_TRIGGER_DONE;
		}
	}

	/*
	 * 如果在安全限制操作结束之前，我们验证 SET CONSTRAINTS
	 * ... IMMEDIATE 已触发所有此类触发器，我们可以允许延迟触发器。
	 * 目前，不用担心。
	 */
	if (fc_deferred_found && InSecurityRestrictedOperation())
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("cannot fire deferred trigger within security-restricted operation")));

	return fc_found;
}

/*
 * afterTriggerInvokeEvents()
 *
 *	扫描给定事件列表，查找标记为在当前触发周期中
 *	触发的事件，并触发它们。
 *
 *	如果 estate 不为 NULL，我们使用它的结果关系信息以避免重复
 *	打开和关闭触发器目标关系。如果它为 NULL，我们
 *	在本地创建一个以缓存信息，以防每个关系有多个触发器
 *	事件。
 *
 *	当 delete_ok 为 true 时，可以安全地删除已经处理完成的事件。
 *	（我们对此并不太在意：如果所有事件都被触发，我们只是将一块重置为
 *	空。此处的目标只是避免在事务结束时触发器排队新事件时
 *	无用地重新扫描事件，因此在只有
 *	一些事件被触发的情况下无需过多担心。）
 *
 *	如果列表中没有未触发的事件，则返回 true （这允许我们
 *	避免重复调用 afterTriggerMarkEvents）。
 */
static bool fc_afterTriggerInvokeEvents(AfterTriggerEventList *fc_events,
						 CommandId fc_firing_id,
						 EState *fc_estate,
						 bool fc_delete_ok)
{
	bool		fc_all_fired = true;
	AfterTriggerEventChunk *fc_chunk;
	MemoryContext fc_per_tuple_context;
	bool		fc_local_estate = false;
	ResultRelInfo *fc_rInfo = NULL;
	Relation	fc_rel = NULL;
	TriggerDesc *fc_trigdesc = NULL;
	FmgrInfo   *fc_finfo = NULL;
	Instrumentation *fc_instr = NULL;
	TupleTableSlot *fc_slot1 = NULL,
			   *fc_slot2 = NULL;

	/* 如果需要，创建一个本地 EState */
	if (fc_estate == NULL)
	{
		fc_estate = CreateExecutorState();
		fc_local_estate = true;
	}

	/* 为触发器函数调用创建每个元组的内存上下文 */
	fc_per_tuple_context =
		AllocSetContextCreate(CurrentMemoryContext,
							  "AfterTriggerTupleContext",
							  ALLOCSET_DEFAULT_SIZES);

	for_each_chunk(fc_chunk, *fc_events)
	{
		AfterTriggerEvent fc_event;
		bool		fc_all_fired_in_chunk = true;

		for_each_event(fc_event, fc_chunk)
		{
			AfterTriggerShared fc_evtshared = GetTriggerSharedData(fc_event);

			/*
			 * 这是我需要触发的吗？
			 */
			if ((fc_event->ate_flags & AFTER_TRIGGER_IN_PROGRESS) &&
				fc_evtshared->ats_firing_id == fc_firing_id)
			{
				ResultRelInfo *fc_src_rInfo,
						   *fc_dst_rInfo;

				/*
				 * 所以让我们触发它……但首先，如果这不是之前相同的关系，就找出正确的关系。
				 */
				if (fc_rel == NULL || RelationGetRelid(fc_rel) != fc_evtshared->ats_relid)
				{
					fc_rInfo = ExecGetTriggerResultRel(fc_estate, fc_evtshared->ats_relid,
													NULL);
					fc_rel = fc_rInfo->ri_RelationDesc;
					/* 捕获引用缓存计数不足的调用 */
					Assert(!RelationHasReferenceCountZero(fc_rel));
					fc_trigdesc = fc_rInfo->ri_TrigDesc;
					/* 小心：trigdesc 可能为 NULL */
					fc_finfo = fc_rInfo->ri_TrigFunctions;
					fc_instr = fc_rInfo->ri_TrigInstrument;
					if (fc_slot1 != NULL)
					{
						ExecDropSingleTupleTableSlot(fc_slot1);
						ExecDropSingleTupleTableSlot(fc_slot2);
						fc_slot1 = fc_slot2 = NULL;
					}
					if (fc_rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
					{
						fc_slot1 = MakeSingleTupleTableSlot(fc_rel->rd_att,
														 &TTSOpsMinimalTuple);
						fc_slot2 = MakeSingleTupleTableSlot(fc_rel->rd_att,
														 &TTSOpsMinimalTuple);
					}
				}

				/*
				 * 查找交叉分区更新事件的源和目标分区结果关系。
				 */
				if ((fc_event->ate_flags & AFTER_TRIGGER_TUP_BITS) ==
					AFTER_TRIGGER_CP_UPDATE)
				{
					Assert(OidIsValid(fc_event->ate_src_part) &&
						   OidIsValid(fc_event->ate_dst_part));
					fc_src_rInfo = ExecGetTriggerResultRel(fc_estate,
														fc_event->ate_src_part,
														fc_rInfo);
					fc_dst_rInfo = ExecGetTriggerResultRel(fc_estate,
														fc_event->ate_dst_part,
														fc_rInfo);
				}
				else
					fc_src_rInfo = fc_dst_rInfo = fc_rInfo;

				/*
				 * 触发它。请注意，AFTER_TRIGGER_IN_PROGRESS 标志仍然被设置，
				 * 因此事件列表的递归检查不会尝试重新触发它。
				 */
				fc_AfterTriggerExecute(fc_estate, fc_event, fc_rInfo,
									fc_src_rInfo, fc_dst_rInfo,
									fc_trigdesc, fc_finfo, fc_instr,
									fc_per_tuple_context, fc_slot1, fc_slot2);

				/*
				 * 将事件标记为完成。
				 */
				fc_event->ate_flags &= ~AFTER_TRIGGER_IN_PROGRESS;
				fc_event->ate_flags |= AFTER_TRIGGER_DONE;
			}
			else if (!(fc_event->ate_flags & AFTER_TRIGGER_DONE))
			{
				/* 还有一些事情需要完成 */
				fc_all_fired = fc_all_fired_in_chunk = false;
			}
		}

		/* 如果 delete_ok，且没有剩余的感兴趣内容，则清除区块 */
		if (fc_delete_ok && fc_all_fired_in_chunk)
		{
			fc_chunk->freeptr = CHUNK_DATA_START(fc_chunk);
			fc_chunk->endfree = fc_chunk->endptr;

			/*
			 * 如果这是最后一个区块，必须同步事件列表的尾部空闲。
			 * 请注意，如果可能存在其他的 AfterTriggerEventList 值指向该事件
			 * 列表，则 delete_ok 绝不能作为 true 传递，因为我们将无法修复它们的
			 * 尾部空闲副本。
			 */
			if (fc_chunk == fc_events->tail)
				fc_events->tailfree = fc_chunk->freeptr;
		}
	}
	if (fc_slot1 != NULL)
	{
		ExecDropSingleTupleTableSlot(fc_slot1);
		ExecDropSingleTupleTableSlot(fc_slot2);
	}

	/* 释放工作资源 */
	MemoryContextDelete(fc_per_tuple_context);

	if (fc_local_estate)
	{
		ExecCloseResultRelations(fc_estate);
		ExecResetTupleTable(fc_estate->es_tupleTable, false);
		FreeExecutorState(fc_estate);
	}

	return fc_all_fired;
}


/*
 * GetAfterTriggersTableData
 *
 * 为指定的触发事件（关系 + 操作类型）查找或创建一个 AfterTriggersTableData 结构。
 * 忽略标记为“关闭”的现有结构；我们不想向它们添加任何额外的元组，
 * 也不想改变它们的 stmt-triggers-fired 状态。
 *
 * 注意：AfterTriggersTableData 列表是在当前（子）事务的 CurTransactionContext 中分配的。
 * 这没问题，因为我们不需要它在 AfterTriggerEndQuery 之后继续存在。
 */
static AfterTriggersTableData *
fc_GetAfterTriggersTableData(Oid fc_relid, CmdType fc_cmdType)
{
	AfterTriggersTableData *fc_table;
	AfterTriggersQueryData *fc_qs;
	MemoryContext fc_oldcxt;
	ListCell   *fc_lc;

	/* 调用者应确保 query_depth 是正常的。 */
	Assert(afterTriggers.query_depth >= 0 &&
		   afterTriggers.query_depth < afterTriggers.maxquerydepth);
	fc_qs = &afterTriggers.query_stack[afterTriggers.query_depth];

	foreach(fc_lc, fc_qs->tables)
	{
		fc_table = (AfterTriggersTableData *) lfirst(fc_lc);
		if (fc_table->relid == fc_relid && fc_table->cmdType == fc_cmdType &&
			!fc_table->closed)
			return fc_table;
	}

	fc_oldcxt = MemoryContextSwitchTo(CurTransactionContext);

	fc_table = (AfterTriggersTableData *) palloc0(sizeof(AfterTriggersTableData));
	fc_table->relid = fc_relid;
	fc_table->cmdType = fc_cmdType;
	fc_qs->tables = lappend(fc_qs->tables, fc_table);

	MemoryContextSwitchTo(fc_oldcxt);

	return fc_table;
}

/*
 * 返回一个适合存放要放入 AfterTriggersTableData 的过渡表元组存储的 TupleTableSlot。
 */
static TupleTableSlot * fc_GetAfterTriggersStoreSlot(AfterTriggersTableData *fc_table,
						  TupleDesc fc_tupdesc)
{
	/* 如果尚未完成，则创建它。 */
	if (!fc_table->storeslot)
	{
		MemoryContext fc_oldcxt;

		/*
		 * 我们只需要这个槽直到 AfterTriggerEndQuery，但让它持续到子事务结束也足够好。
		 * 它将在 AfterTriggerFreeQuery() 中被释放。然而，传入的 tupdesc 可能有
		 * 不同的生命周期，所以我们最好先复制一份。
		 */
		fc_oldcxt = MemoryContextSwitchTo(CurTransactionContext);
		fc_tupdesc = CreateTupleDescCopy(fc_tupdesc);
		fc_table->storeslot = MakeSingleTupleTableSlot(fc_tupdesc, &TTSOpsVirtual);
		MemoryContextSwitchTo(fc_oldcxt);
	}

	return fc_table->storeslot;
}

/*
 * MakeTransitionCaptureState
 *
 * 为给定的 TriggerDesc、目标关系和操作类型创建一个 TransitionCaptureState 对象。 TCS 对象保存了所有所需的状态，以决定是否在过渡表中捕获元组。
 *
 * 如果 'trigdesc' 中没有请求相关过渡表的触发器，则返回 NULL。
 *
 * 生成的对象可以传递给 ExecAR* 函数。当处理子表时，调用方可以设置 tcs_original_insert_tuple，以避免在根表的格式中重新构造原始元组。
 *
 * 请注意，我们将标志从父表复制到此结构中（而不是随后直接使用关系的 TriggerDesc），以便我们可以用它来控制从子表收集过渡元组。
 *
 * 根据 SQL 规范，在同一查询中对同一表进行的所有相同类型的操作（INSERT/UPDATE/DELETE）应共享一个过渡表。因此，Tuplestores 由使用表 OID + CmdType 查找的 AfterTriggersTableData 结构拥有，并仅由我们交给调用者的 TransitionCaptureState 对象引用。
 */
TransitionCaptureState *
MakeTransitionCaptureState(TriggerDesc *fc_trigdesc, Oid fc_relid, CmdType fc_cmdType)
{
	TransitionCaptureState *fc_state;
	bool		fc_need_old_upd,
				fc_need_new_upd,
				fc_need_old_del,
				fc_need_new_ins;
	AfterTriggersTableData *fc_table;
	MemoryContext fc_oldcxt;
	ResourceOwner fc_saveResourceOwner;

	if (fc_trigdesc == NULL)
		return NULL;

	/* 检测我们需要哪些表。 */
	switch (fc_cmdType)
	{
		case CMD_INSERT:
			fc_need_old_upd = fc_need_old_del = fc_need_new_upd = false;
			fc_need_new_ins = fc_trigdesc->trig_insert_new_table;
			break;
		case CMD_UPDATE:
			fc_need_old_upd = fc_trigdesc->trig_update_old_table;
			fc_need_new_upd = fc_trigdesc->trig_update_new_table;
			fc_need_old_del = fc_need_new_ins = false;
			break;
		case CMD_DELETE:
			fc_need_old_del = fc_trigdesc->trig_delete_old_table;
			fc_need_old_upd = fc_need_new_upd = fc_need_new_ins = false;
			break;
		case CMD_MERGE:
			fc_need_old_upd = fc_trigdesc->trig_update_old_table;
			fc_need_new_upd = fc_trigdesc->trig_update_new_table;
			fc_need_old_del = fc_trigdesc->trig_delete_old_table;
			fc_need_new_ins = fc_trigdesc->trig_insert_new_table;
			break;
		default:
			elog(ERROR, "unexpected CmdType: %d", (int) fc_cmdType);
			/* 保持编译器安静 */
			fc_need_old_upd = fc_need_new_upd = fc_need_old_del = fc_need_new_ins = false;
			break;
	}
	if (!fc_need_old_upd && !fc_need_new_upd && !fc_need_new_ins && !fc_need_old_del)
		return NULL;

	/* 检查状态，如 AfterTriggerSaveEvent。 */
	if (afterTriggers.query_depth < 0)
		elog(ERROR, "MakeTransitionCaptureState() called outside of query");

	/* 确保我们有足够的空间在这个查询深度记录事件。 */
	if (afterTriggers.query_depth >= afterTriggers.maxquerydepth)
		fc_AfterTriggerEnlargeQueryState();

	/*
	 * 查找或创建一个 AfterTriggersTableData 结构来保存
	 * tuplestore(s)。如果找到匹配的结构但标记为关闭，
	 * 则忽略它；我们需要一个更新的结构。
	 *
	 * 注意：AfterTriggersTableData 列表以及 tuplestores
	 * 都是在当前（子）事务的 CurTransactionContext 中分配的，
	 * tuplestores 由（子）事务的资源所有者管理。
	 * 这具有足够的生命周期，因为我们不允许使用
	 * 过渡表的触发器是可延迟的；它们将在
	 * AfterTriggerEndQuery 时触发，此后可以删除数据。
	 */
	fc_table = fc_GetAfterTriggersTableData(fc_relid, fc_cmdType);

	/* 现在创建所需的 tuplestore(s)，如果还没有的话。 */
	fc_oldcxt = MemoryContextSwitchTo(CurTransactionContext);
	fc_saveResourceOwner = CurrentResourceOwner;
	CurrentResourceOwner = CurTransactionResourceOwner;

	if (fc_need_old_upd && fc_table->old_upd_tuplestore == NULL)
		fc_table->old_upd_tuplestore = tuplestore_begin_heap(false, false, work_mem);
	if (fc_need_new_upd && fc_table->new_upd_tuplestore == NULL)
		fc_table->new_upd_tuplestore = tuplestore_begin_heap(false, false, work_mem);
	if (fc_need_old_del && fc_table->old_del_tuplestore == NULL)
		fc_table->old_del_tuplestore = tuplestore_begin_heap(false, false, work_mem);
	if (fc_need_new_ins && fc_table->new_ins_tuplestore == NULL)
		fc_table->new_ins_tuplestore = tuplestore_begin_heap(false, false, work_mem);

	CurrentResourceOwner = fc_saveResourceOwner;
	MemoryContextSwitchTo(fc_oldcxt);

	/* 现在在调用者的上下文中构建 TransitionCaptureState 结构 */
	fc_state = (TransitionCaptureState *) palloc0(sizeof(TransitionCaptureState));
	fc_state->tcs_delete_old_table = fc_trigdesc->trig_delete_old_table;
	fc_state->tcs_update_old_table = fc_trigdesc->trig_update_old_table;
	fc_state->tcs_update_new_table = fc_trigdesc->trig_update_new_table;
	fc_state->tcs_insert_new_table = fc_trigdesc->trig_insert_new_table;
	fc_state->tcs_private = fc_table;

	return fc_state;
}


/* ----------
 * AfterTriggerBeginXact()
 *
 * 在事务开始时调用（无论是 BEGIN 还是单个
 * 语句的隐式调用，不在事务块中）。
 * ----------
 */
void AfterTriggerBeginXact(void)
{
	/*
	 * 将后触发器状态结构初始化为空
	 */
	afterTriggers.firing_counter = (CommandId) 1;	/* 不能是 0 */
	afterTriggers.query_depth = -1;

	/*
	 * 验证是否没有剩余状态。 如果这些断言失败，
	 * 则意味着未调用 AfterTriggerEndXact 或未正确清理。
	 */
	Assert(afterTriggers.state == NULL);
	Assert(afterTriggers.query_stack == NULL);
	Assert(afterTriggers.maxquerydepth == 0);
	Assert(afterTriggers.event_cxt == NULL);
	Assert(afterTriggers.events.head == NULL);
	Assert(afterTriggers.trans_stack == NULL);
	Assert(afterTriggers.maxtransdepth == 0);
}


/* ----------
 * AfterTriggerBeginQuery()
 *
 * 在我们开始在事务（或子事务）中处理单个查询之前调用。
 * 大部分实际工作会被推迟，直到有人实际尝试排队触发器事件。
 * ----------
 */
void AfterTriggerBeginQuery(void)
{
	/* 增加查询栈深度 */
	afterTriggers.query_depth++;
}


/* ----------
 * AfterTriggerEndQuery()
 *
 * 在一个查询被完全处理后调用此函数。此时我们调用所有由查询排队的AFTER IMMEDIATE触发事件，并将延迟触发事件转移到全局延迟触发列表中。
 *
 * 请注意，这必须在关闭执行器ExecutorEnd之前调用，因为我们利用了EState关于目标关系的信息。通常它是在ExecutorFinish中调用的。
 * ----------
 */
void AfterTriggerEndQuery(EState *fc_estate)
{
	AfterTriggersQueryData *fc_qs;

	/* 也必须在查询内部 */
	Assert(afterTriggers.query_depth >= 0);

	/*
	 * 如果我们甚至没有初始化事件栈，就肯定不会有任何事件，所以快速退出。
	 */
	if (afterTriggers.query_depth >= afterTriggers.maxquerydepth)
	{
		afterTriggers.query_depth--;
		return;
	}

	/*
	 * 处理查询排队的所有即刻模式触发器，并将延迟的触发器移动到主要的延迟事件列表中。
	 *
	 * 请注意，我们在实际触发任何事件之前决定哪些将被触发，并将延迟的事件放入主要列表。这确保了如果触发函数执行SET CONSTRAINTS ... IMMEDIATE时，我们决定延迟的所有事件将可供其触发。
	 *
	 * 我们循环以防触发器在同一查询级别排队更多事件。普通触发器函数，包括所有PL/pgSQL触发器函数，则会在专用查询级别触发任何触发器。外键约束触发器确实会添加到当前查询级别，这是由于它们将fire_triggers = false传递给SPI_execute_snapshot()。其他C语言触发器可能也会这样做。
	 *
	 * 如果我们没有找到可触发的事件，则不需要增加firing_counter。
	 */
	fc_qs = &afterTriggers.query_stack[afterTriggers.query_depth];

	for (;;)
	{
		if (fc_afterTriggerMarkEvents(&fc_qs->events, &afterTriggers.events, true))
		{
			CommandId	fc_firing_id = afterTriggers.firing_counter++;
			AfterTriggerEventChunk *fc_oldtail = fc_qs->events.tail;

			if (fc_afterTriggerInvokeEvents(&fc_qs->events, fc_firing_id, fc_estate, false))
				break;			/* 所有已触发 */

			/*
			 * 触发一个触发器可能会导致query_stack被重新分配内存，
			 * 所以我们必须在每次调用afterTriggerInvokeEvents后重新计算qs。此外，传递delete_ok = true在这里是不安全的，
			 * 因为这可能导致afterTriggerInvokeEvents在栈被重新分配后尝试访问qs->events。
			 */
			fc_qs = &afterTriggers.query_stack[afterTriggers.query_depth];

			/*
			 * 我们需要再次扫描事件列表。为了减少这样做的成本，去掉完全触发的块。我们知道在afterTriggerMarkEvents结束时，
			 * 所有事件都被标记为IN_PROGRESS或DONE，因此任何仍然有趣的事件必须是在此之后添加的，因此必须在当时的尾部块中或在后来的块中。
			 * 所以，删除所有位于oldtail之前的块。这与我们通过传递delete_ok = true而去掉的事件集大致相同。
			 */
			Assert(fc_oldtail != NULL);
			while (fc_qs->events.head != fc_oldtail)
				fc_afterTriggerDeleteHeadEventChunk(fc_qs);
		}
		else
			break;
	}

	/* 释放查询级别的本地存储，包括tuplestores（如果有的话） */
	fc_AfterTriggerFreeQuery(&afterTriggers.query_stack[afterTriggers.query_depth]);

	afterTriggers.query_depth--;
}


/*
 * AfterTriggerFreeQuery
 * 释放触发器查询级别的附属存储。
 * 这包括关闭tuplestores。
 * 注意：如果在出错后被中断并再次为同一查询级别调用它，确保这一点是安全的很重要。
 */
static void fc_AfterTriggerFreeQuery(AfterTriggersQueryData *fc_qs)
{
	Tuplestorestate *fc_ts;
	List	   *fc_tables;
	ListCell   *fc_lc;

	/* 删除触发器事件 */
	fc_afterTriggerFreeEventList(&fc_qs->events);

	/* 删除FDW tuplestore（如果有的话） */
	fc_ts = fc_qs->fdw_tuplestore;
	fc_qs->fdw_tuplestore = NULL;
	if (fc_ts)
		tuplestore_end(fc_ts);

	/* 释放每个表的附属存储 */
	fc_tables = fc_qs->tables;
	foreach(fc_lc, fc_tables)
	{
		AfterTriggersTableData *fc_table = (AfterTriggersTableData *) lfirst(fc_lc);

		fc_ts = fc_table->old_upd_tuplestore;
		fc_table->old_upd_tuplestore = NULL;
		if (fc_ts)
			tuplestore_end(fc_ts);
		fc_ts = fc_table->new_upd_tuplestore;
		fc_table->new_upd_tuplestore = NULL;
		if (fc_ts)
			tuplestore_end(fc_ts);
		fc_ts = fc_table->old_del_tuplestore;
		fc_table->old_del_tuplestore = NULL;
		if (fc_ts)
			tuplestore_end(fc_ts);
		fc_ts = fc_table->new_ins_tuplestore;
		fc_table->new_ins_tuplestore = NULL;
		if (fc_ts)
			tuplestore_end(fc_ts);
		if (fc_table->storeslot)
		{
			TupleTableSlot *fc_slot = fc_table->storeslot;

			fc_table->storeslot = NULL;
			ExecDropSingleTupleTableSlot(fc_slot);
		}
	}

	/*
	 * 现在释放 AfterTriggersTableData 结构体和列表单元格。首先重置列表指针；如果 list_free_deep 不小心出现错误，泄漏该存储比进入无限循环要好。
	 */
	fc_qs->tables = NIL;
	list_free_deep(fc_tables);
}


/* ----------
 * AfterTriggerFireDeferred()
 *
 *	在当前事务提交之前调用。在此时，我们调用所有待处理的延迟触发器。
 *
 *	在预提交处理期间，其他模块可能会排队额外的延迟触发器；因此 xact.c 可能需要多次调用此函数。
 * ----------
 */
void AfterTriggerFireDeferred(void)
{
	AfterTriggerEventList *fc_events;
	bool		fc_snap_pushed = false;

	/* 不能在查询内部 */
	Assert(afterTriggers.query_depth == -1);

	/*
	 * 如果有任何触发器需要触发，请确保我们已设置快照供它们使用。 （由于 PortalRunUtility 不为 COMMIT 设置快照，我们不能假设 ActiveSnapshot 在进入时是有效的。）
	 */
	fc_events = &afterTriggers.events;
	if (fc_events->head != NULL)
	{
		PushActiveSnapshot(GetTransactionSnapshot());
		fc_snap_pushed = true;
	}

	/*
	 * 运行所有剩余的触发器。循环，直到它们都处理完，以防某些触发器为我们排队更多的工作。
	 */
	while (fc_afterTriggerMarkEvents(fc_events, NULL, false))
	{
		CommandId	fc_firing_id = afterTriggers.firing_counter++;

		if (fc_afterTriggerInvokeEvents(fc_events, fc_firing_id, NULL, true))
			break;				/* 所有已触发 */
	}

	/*
	 * 我们不费心释放事件列表，因为它最终会消失
	 * （并且比通过 pfree 更有效）在 AfterTriggerEndXact 中。
	 */

	if (fc_snap_pushed)
		PopActiveSnapshot();
}


/* ----------
 * AfterTriggerEndXact()
 *
 *	当前事务正在结束。
 *
 *	任何未触发的触发器都被取消，因此我们简单地丢弃
 *	我们知道的任何东西。
 *
 *	注意：在事务中止时可能会多次调用此函数；因此，如果
 *	已经关闭，不要抱怨。
 * ----------
 */
void AfterTriggerEndXact(bool fc_isCommit)
{
	/*
	 * 忘记待处理事件列表。
	 *
	 * 由于所有信息都在 TopTransactionContext 或其子项中，我们
	 * 不需要做任何事情来回收内存。然而，待处理事件列表可能很大，因此尽早丢弃它是有用的——特别是当我们因为列表耗尽内存而中止时！
	 */
	if (afterTriggers.event_cxt)
	{
		MemoryContextDelete(afterTriggers.event_cxt);
		afterTriggers.event_cxt = NULL;
		afterTriggers.events.head = NULL;
		afterTriggers.events.tail = NULL;
		afterTriggers.events.tailfree = NULL;
	}

	/*
	 * 也忘记任何子事务状态。由于这不会很大，我们让最终的 TopTransactionContext 重置来释放内存，而不是在这里处理。
	 */
	afterTriggers.trans_stack = NULL;
	afterTriggers.maxtransdepth = 0;


	/*
	 * 忘记查询栈和约束相关的状态信息。与子事务状态信息一样，我们在这里不费心释放内存。
	 */
	afterTriggers.query_stack = NULL;
	afterTriggers.maxquerydepth = 0;
	afterTriggers.state = NULL;

	/* 直到下一个事务开始，不再进行 afterTriggers 操作。 */
	afterTriggers.query_depth = -1;
}

/*
 * AfterTriggerBeginSubXact()
 *
 *	开始一个子事务。
 */
void AfterTriggerBeginSubXact(void)
{
	int			fc_my_level = GetCurrentTransactionNestLevel();

	/*
	 * 如果需要，在 trans_stack 中分配更多空间。 （注意：由于子事务的最小嵌套级别为 2，我们浪费了数组的前几个条目；不值得费心避免。）
	 */
	while (fc_my_level >= afterTriggers.maxtransdepth)
	{
		if (afterTriggers.maxtransdepth == 0)
		{
			/* 任意初始化最多 8 个子事务级别 */
			afterTriggers.trans_stack = (AfterTriggersTransData *)
				MemoryContextAlloc(TopTransactionContext,
								   8 * sizeof(AfterTriggersTransData));
			afterTriggers.maxtransdepth = 8;
		}
		else
		{
			/* repalloc 将保留栈在同一上下文中 */
			int			fc_new_alloc = afterTriggers.maxtransdepth * 2;

			afterTriggers.trans_stack = (AfterTriggersTransData *)
				repalloc(afterTriggers.trans_stack,
						 fc_new_alloc * sizeof(AfterTriggersTransData));
			afterTriggers.maxtransdepth = fc_new_alloc;
		}
	}

	/*
	 * 将当前信息推入栈中。SET CONSTRAINTS 状态
	 * 在更改之前不会保存。同样，直到需要时我们才会创建
	 * 每个子事务的事件上下文。
	 */
	afterTriggers.trans_stack[fc_my_level].state = NULL;
	afterTriggers.trans_stack[fc_my_level].events = afterTriggers.events;
	afterTriggers.trans_stack[fc_my_level].query_depth = afterTriggers.query_depth;
	afterTriggers.trans_stack[fc_my_level].firing_counter = afterTriggers.firing_counter;
}

/*
 * AfterTriggerEndSubXact()
 *
 * 当前子事务正在结束。
 */
void AfterTriggerEndSubXact(bool fc_isCommit)
{
	int			fc_my_level = GetCurrentTransactionNestLevel();
	SetConstraintState fc_state;
	AfterTriggerEvent fc_event;
	AfterTriggerEventChunk *fc_chunk;
	CommandId	fc_subxact_firing_id;

	/*
	 * 如果需要，弹出之前的状态。
	 */
	if (fc_isCommit)
	{
		Assert(fc_my_level < afterTriggers.maxtransdepth);
		/* 如果我们保存了之前的状态，已经不再需要它了 */
		fc_state = afterTriggers.trans_stack[fc_my_level].state;
		if (fc_state != NULL)
			pfree(fc_state);
		/* 这避免了如果后面出错而导致的双重 pfree： */
		afterTriggers.trans_stack[fc_my_level].state = NULL;
		Assert(afterTriggers.query_depth ==
			   afterTriggers.trans_stack[fc_my_level].query_depth);
	}
	else
	{
		/*
		 * 正在中止。子事务启动可能在调用
		 * AfterTriggerBeginSubXact 之前失败，此时我们不能冒险触碰
		 * 不存在的 trans_stack 层级。
		 */
		if (fc_my_level >= afterTriggers.maxtransdepth)
			return;

		/*
		 * 释放正在中止的查询的查询级存储，并将
		 * query_depth 恢复到其前子事务值。这假设一个
		 * 子事务不会向在早期事务状态中启动的查询级添加事件。
		 */
		while (afterTriggers.query_depth > afterTriggers.trans_stack[fc_my_level].query_depth)
		{
			if (afterTriggers.query_depth < afterTriggers.maxquerydepth)
				fc_AfterTriggerFreeQuery(&afterTriggers.query_stack[afterTriggers.query_depth]);
			afterTriggers.query_depth--;
		}
		Assert(afterTriggers.query_depth ==
			   afterTriggers.trans_stack[fc_my_level].query_depth);

		/*
		 * 恢复全局延迟事件列表到其之前的长度，
		 * 丢弃子事务排队的任何事件。
		 */
		fc_afterTriggerRestoreEventList(&afterTriggers.events,
									 &afterTriggers.trans_stack[fc_my_level].events);

		/*
		 * 恢复触发器状态。如果保存的状态为 NULL，则这个
		 * 子事务没有保存它，因此不需要恢复。
		 */
		fc_state = afterTriggers.trans_stack[fc_my_level].state;
		if (fc_state != NULL)
		{
			pfree(afterTriggers.state);
			afterTriggers.state = fc_state;
		}
		/* 这避免了如果后面出错而导致的双重 pfree： */
		afterTriggers.trans_stack[fc_my_level].state = NULL;

		/*
		 * 扫描此子事务或子事务的子级标记为 DONE 或 IN
		 * PROGRESS 的任何剩余延迟事件，并取消标记它们。我们可以
		 * 识别这些事件，因为它们的触发 ID 大于或
		 * 等于我们在子事务开始时保存的 firing_counter 值。
		 * （这基本假设当前子事务包括所有
		 * 在其之后启动的子事务。）
		 */
		fc_subxact_firing_id = afterTriggers.trans_stack[fc_my_level].firing_counter;
		for_each_event_chunk(fc_event, fc_chunk, afterTriggers.events)
		{
			AfterTriggerShared fc_evtshared = GetTriggerSharedData(fc_event);

			if (fc_event->ate_flags &
				(AFTER_TRIGGER_DONE | AFTER_TRIGGER_IN_PROGRESS))
			{
				if (fc_evtshared->ats_firing_id >= fc_subxact_firing_id)
					fc_event->ate_flags &=
						~(AFTER_TRIGGER_DONE | AFTER_TRIGGER_IN_PROGRESS);
			}
		}
	}
}

/*
 * 获取给定事件的转换表，并根据我们是
 * 处理旧元组还是新元组而有所不同。
 */
static Tuplestorestate * fc_GetAfterTriggersTransitionTable(int fc_event,
								TupleTableSlot *fc_oldslot,
								TupleTableSlot *fc_newslot,
								TransitionCaptureState *fc_transition_capture)
{
	Tuplestorestate *fc_tuplestore = NULL;
	bool		fc_delete_old_table = fc_transition_capture->tcs_delete_old_table;
	bool		fc_update_old_table = fc_transition_capture->tcs_update_old_table;
	bool		fc_update_new_table = fc_transition_capture->tcs_update_new_table;
	bool		fc_insert_new_table = fc_transition_capture->tcs_insert_new_table;

	/*
	 * 对于 INSERT 事件，NEW 应该是非 NULL，对于 DELETE 事件，OLD 应该
	 * 是非 NULL，而对于 UPDATE 事件，通常 BOTH OLD 和 NEW 是
	 * 非 NULL。但对于在 UPDATE 分区键行移动期间触发的用于捕获转换元组的
	 * UPDATE 事件，当事件是针对正在插入的行时 OLD 为 NULL，而当事件是
	 * 针对正在删除的行时 NEW 为 NULL。
	 */
	Assert(!(fc_event == TRIGGER_EVENT_DELETE && fc_delete_old_table &&
			 TupIsNull(fc_oldslot)));
	Assert(!(fc_event == TRIGGER_EVENT_INSERT && fc_insert_new_table &&
			 TupIsNull(fc_newslot)));

	if (!TupIsNull(fc_oldslot))
	{
		Assert(TupIsNull(fc_newslot));
		if (fc_event == TRIGGER_EVENT_DELETE && fc_delete_old_table)
			fc_tuplestore = fc_transition_capture->tcs_private->old_del_tuplestore;
		else if (fc_event == TRIGGER_EVENT_UPDATE && fc_update_old_table)
			fc_tuplestore = fc_transition_capture->tcs_private->old_upd_tuplestore;
	}
	else if (!TupIsNull(fc_newslot))
	{
		Assert(TupIsNull(fc_oldslot));
		if (fc_event == TRIGGER_EVENT_INSERT && fc_insert_new_table)
			fc_tuplestore = fc_transition_capture->tcs_private->new_ins_tuplestore;
		else if (fc_event == TRIGGER_EVENT_UPDATE && fc_update_new_table)
			fc_tuplestore = fc_transition_capture->tcs_private->new_upd_tuplestore;
	}

	return fc_tuplestore;
}

/*
 * 将给定的堆元组添加到给定的元组存储中，如果必要则应用
 * 转换映射。
 *
 * 如果提供了 original_insert_tuple，我们可以在不转换的情况下添加该元组。
 */
static void fc_TransitionTableAddTuple(EState *fc_estate,
						TransitionCaptureState *fc_transition_capture,
						ResultRelInfo *fc_relinfo,
						TupleTableSlot *fc_slot,
						TupleTableSlot *fc_original_insert_tuple,
						Tuplestorestate *fc_tuplestore)
{
	TupleConversionMap *fc_map;

	/*
	 * 如果我们没有元组存储，则不需要执行任何操作。
	 */
	if (fc_tuplestore == NULL)
		return;

	if (fc_original_insert_tuple)
		tuplestore_puttupleslot(fc_tuplestore, fc_original_insert_tuple);
	else if ((fc_map = ExecGetChildToRootMap(fc_relinfo)) != NULL)
	{
		AfterTriggersTableData *fc_table = fc_transition_capture->tcs_private;
		TupleTableSlot *fc_storeslot;

		fc_storeslot = fc_GetAfterTriggersStoreSlot(fc_table, fc_map->outdesc);
		execute_attr_map_slot(fc_map->attrMap, fc_slot, fc_storeslot);
		tuplestore_puttupleslot(fc_tuplestore, fc_storeslot);
	}
	else
		tuplestore_puttupleslot(fc_tuplestore, fc_slot);
}

/* ----------
 * AfterTriggerEnlargeQueryState()
 *
 * 准备必要的状态，以便我们可以记录查询排队的 AFTER 触发器事件。
 * 允许在（子）事务内有嵌套查询，因此我们需要为每个查询
 * 嵌套级别拥有独立的状态。
 * ----------
 */
static void fc_AfterTriggerEnlargeQueryState(void)
{
	int			fc_init_depth = afterTriggers.maxquerydepth;

	Assert(afterTriggers.query_depth >= afterTriggers.maxquerydepth);

	if (afterTriggers.maxquerydepth == 0)
	{
		int			fc_new_alloc = Max(afterTriggers.query_depth + 1, 8);

		afterTriggers.query_stack = (AfterTriggersQueryData *)
			MemoryContextAlloc(TopTransactionContext,
							   fc_new_alloc * sizeof(AfterTriggersQueryData));
		afterTriggers.maxquerydepth = fc_new_alloc;
	}
	else
	{
		/* repalloc 将保留栈在同一上下文中 */
		int			fc_old_alloc = afterTriggers.maxquerydepth;
		int			fc_new_alloc = Max(afterTriggers.query_depth + 1,
									fc_old_alloc * 2);

		afterTriggers.query_stack = (AfterTriggersQueryData *)
			repalloc(afterTriggers.query_stack,
					 fc_new_alloc * sizeof(AfterTriggersQueryData));
		afterTriggers.maxquerydepth = fc_new_alloc;
	}

	/* 将新数组条目初始化为空 */
	while (fc_init_depth < afterTriggers.maxquerydepth)
	{
		AfterTriggersQueryData *fc_qs = &afterTriggers.query_stack[fc_init_depth];

		fc_qs->events.head = NULL;
		fc_qs->events.tail = NULL;
		fc_qs->events.tailfree = NULL;
		fc_qs->fdw_tuplestore = NULL;
		fc_qs->tables = NIL;

		++fc_init_depth;
	}
}

/*
 * 创建一个空的 SetConstraintState，预留 numalloc trigstates 的空间
 */
static SetConstraintState fc_SetConstraintStateCreate(int fc_numalloc)
{
	SetConstraintState fc_state;

	/* 对于 numalloc == 0 的情况要理智处理 */
	if (fc_numalloc <= 0)
		fc_numalloc = 1;

	/*
	 * 我们假设将其零化将正确初始化状态值。
	 */
	fc_state = (SetConstraintState)
		MemoryContextAllocZero(TopTransactionContext,
							   offsetof(SetConstraintStateData, trigstates) +
							   fc_numalloc * sizeof(SetConstraintTriggerData));

	fc_state->numalloc = fc_numalloc;

	return fc_state;
}

/*
 * 复制一个 SetConstraintState
 */
static SetConstraintState fc_SetConstraintStateCopy(SetConstraintState fc_origstate)
{
	SetConstraintState fc_state;

	fc_state = fc_SetConstraintStateCreate(fc_origstate->numstates);

	fc_state->all_isset = fc_origstate->all_isset;
	fc_state->all_isdeferred = fc_origstate->all_isdeferred;
	fc_state->numstates = fc_origstate->numstates;
	memcpy(fc_state->trigstates, fc_origstate->trigstates,
		   fc_origstate->numstates * sizeof(SetConstraintTriggerData));

	return fc_state;
}

/*
 * 将每个触发器项添加到 SetConstraintState。 返回可能已更改的
 * 指向状态对象的指针（如果我们需要重新分配，它将会更改）。
 */
static SetConstraintState fc_SetConstraintStateAddItem(SetConstraintState fc_state,
						  Oid fc_tgoid, bool fc_tgisdeferred)
{
	if (fc_state->numstates >= fc_state->numalloc)
	{
		int			fc_newalloc = fc_state->numalloc * 2;

		fc_newalloc = Max(fc_newalloc, 8);	/* 如果原始大小为 0 */
		fc_state = (SetConstraintState)
			repalloc(fc_state,
					 offsetof(SetConstraintStateData, trigstates) +
					 fc_newalloc * sizeof(SetConstraintTriggerData));
		fc_state->numalloc = fc_newalloc;
		Assert(fc_state->numstates < fc_state->numalloc);
	}

	fc_state->trigstates[fc_state->numstates].sct_tgoid = fc_tgoid;
	fc_state->trigstates[fc_state->numstates].sct_tgisdeferred = fc_tgisdeferred;
	fc_state->numstates++;

	return fc_state;
}

/* ----------
 * AfterTriggerSetState()
 *
 *	执行 SET CONSTRAINTS ... 工具命令。
 * ----------
 */
void AfterTriggerSetState(ConstraintsSetStmt *fc_stmt)
{
	int			fc_my_level = GetCurrentTransactionNestLevel();

	/* 如果我们还没有这样做，初始化我们的状态。 */
	if (afterTriggers.state == NULL)
		afterTriggers.state = fc_SetConstraintStateCreate(8);

	/*
	 * 如果在子事务中，并且我们尚未保存当前状态，
	 * 请保存它，以便在子事务中止时可以恢复。
	 */
	if (fc_my_level > 1 &&
		afterTriggers.trans_stack[fc_my_level].state == NULL)
	{
		afterTriggers.trans_stack[fc_my_level].state =
			fc_SetConstraintStateCopy(afterTriggers.state);
	}

	/*
	 * 处理 SET CONSTRAINTS ALL ...
	 */
	if (fc_stmt->constraints == NIL)
	{
		/*
		 * 忘记此事务中之前的任何 SET CONSTRAINTS 命令。
		 */
		afterTriggers.state->numstates = 0;

		/*
		 * 将每个事务的 ALL 状态设置为已知。
		 */
		afterTriggers.state->all_isset = true;
		afterTriggers.state->all_isdeferred = fc_stmt->deferred;
	}
	else
	{
		Relation	fc_conrel;
		Relation	fc_tgrel;
		List	   *fc_conoidlist = NIL;
		List	   *fc_tgoidlist = NIL;
		ListCell   *fc_lc;

		/*
		 * 处理 SET CONSTRAINTS constraint-name [, ...]
		 *
		 * 首先，识别所有命名约束并列出它们的
		 * OIDs。由于与 SQL 规范不同，我们允许在一个模式中存在多个
		 * 相同名称的约束，因此这些规范并不一定是唯一的。
		 * 我们的策略是定位第一个具有匹配项的搜索路径模式
		 * 中的所有匹配约束，但忽略搜索第一个匹配项之后的模式中的匹配项。
		 * （这有点奇怪，但这是历史行为。）
		 *
		 * 分区表中的约束可能在分区中具有对应的
		 * 约束。也要获取那些约束。
		 */
		fc_conrel = table_open(ConstraintRelationId, AccessShareLock);

		foreach(fc_lc, fc_stmt->constraints)
		{
			RangeVar   *fc_constraint = lfirst(fc_lc);
			bool		fc_found;
			List	   *fc_namespacelist;
			ListCell   *fc_nslc;

			if (fc_constraint->catalogname)
			{
				if (strcmp(fc_constraint->catalogname, get_database_name(MyDatabaseId)) != 0)
					ereport(ERROR,
							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
							 errmsg("cross-database references are not implemented: \"%s.%s.%s\"",
									fc_constraint->catalogname, fc_constraint->schemaname,
									fc_constraint->relname)));
			}

			/*
			 * 如果给定了带有约束的模式名称，则仅在该模式中查找。
			 * 如果给定一个裸约束名称，请使用
			 * 搜索路径查找第一个匹配的约束。
			 */
			if (fc_constraint->schemaname)
			{
				Oid			fc_namespaceId = LookupExplicitNamespace(fc_constraint->schemaname,
																  false);

				fc_namespacelist = list_make1_oid(fc_namespaceId);
			}
			else
			{
				fc_namespacelist = fetch_search_path(true);
			}

			fc_found = false;
			foreach(fc_nslc, fc_namespacelist)
			{
				Oid			fc_namespaceId = lfirst_oid(fc_nslc);
				SysScanDesc fc_conscan;
				ScanKeyData fc_skey[2];
				HeapTuple	fc_tup;

				ScanKeyInit(&fc_skey[0],
							Anum_pg_constraint_conname,
							BTEqualStrategyNumber, F_NAMEEQ,
							CStringGetDatum(fc_constraint->relname));
				ScanKeyInit(&fc_skey[1],
							Anum_pg_constraint_connamespace,
							BTEqualStrategyNumber, F_OIDEQ,
							ObjectIdGetDatum(fc_namespaceId));

				fc_conscan = systable_beginscan(fc_conrel, ConstraintNameNspIndexId,
											 true, NULL, 2, fc_skey);

				while (HeapTupleIsValid(fc_tup = systable_getnext(fc_conscan)))
				{
					Form_pg_constraint fc_con = (Form_pg_constraint) GETSTRUCT(fc_tup);

					if (fc_con->condeferrable)
						fc_conoidlist = lappend_oid(fc_conoidlist, fc_con->oid);
					else if (fc_stmt->deferred)
						ereport(ERROR,
								(errcode(ERRCODE_WRONG_OBJECT_TYPE),
								 errmsg("constraint \"%s\" is not deferrable",
										fc_constraint->relname)));
					fc_found = true;
				}

				systable_endscan(fc_conscan);

				/*
				 * 一旦我们找到了匹配的约束，就不再搜索
				 * 搜索路径的后续部分。
				 */
				if (fc_found)
					break;
			}

			list_free(fc_namespacelist);

			/*
			 * 没找到？
			 */
			if (!fc_found)
				ereport(ERROR,
						(errcode(ERRCODE_UNDEFINED_OBJECT),
						 errmsg("constraint \"%s\" does not exist",
								fc_constraint->relname)));
		}

		/*
		 * 扫描约束的任何可能的后代。我们将找到的
		 * 任何内容附加到我们正在扫描的同一列表中；
		 * 这将导致我们对那些也进行新的扫描，因此如果还有
		 * 进一步的后代，我们也会捕获到它们。
		 */
		foreach(fc_lc, fc_conoidlist)
		{
			Oid			fc_parent = lfirst_oid(fc_lc);
			ScanKeyData fc_key;
			SysScanDesc fc_scan;
			HeapTuple	fc_tuple;

			ScanKeyInit(&fc_key,
						Anum_pg_constraint_conparentid,
						BTEqualStrategyNumber, F_OIDEQ,
						ObjectIdGetDatum(fc_parent));

			fc_scan = systable_beginscan(fc_conrel, ConstraintParentIndexId, true, NULL, 1, &fc_key);

			while (HeapTupleIsValid(fc_tuple = systable_getnext(fc_scan)))
			{
				Form_pg_constraint fc_con = (Form_pg_constraint) GETSTRUCT(fc_tuple);

				fc_conoidlist = lappend_oid(fc_conoidlist, fc_con->oid);
			}

			systable_endscan(fc_scan);
		}

		table_close(fc_conrel, AccessShareLock);

		/*
		 * 现在，定位实现每个这些约束的触发器，
		 * 并列出它们的 OIDs。
		 */
		fc_tgrel = table_open(TriggerRelationId, AccessShareLock);

		foreach(fc_lc, fc_conoidlist)
		{
			Oid			fc_conoid = lfirst_oid(fc_lc);
			ScanKeyData fc_skey;
			SysScanDesc fc_tgscan;
			HeapTuple	fc_htup;

			ScanKeyInit(&fc_skey,
						Anum_pg_trigger_tgconstraint,
						BTEqualStrategyNumber, F_OIDEQ,
						ObjectIdGetDatum(fc_conoid));

			fc_tgscan = systable_beginscan(fc_tgrel, TriggerConstraintIndexId, true,
										NULL, 1, &fc_skey);

			while (HeapTupleIsValid(fc_htup = systable_getnext(fc_tgscan)))
			{
				Form_pg_trigger fc_pg_trigger = (Form_pg_trigger) GETSTRUCT(fc_htup);

				/*
				 * 在 pg_trigger 中标记为不可推迟的触发器将被静默跳过。
				 * 这不是错误条件，因为可推迟的 RI 约束可能具有一些不可推迟的
				 * 操作。
				 */
				if (fc_pg_trigger->tgdeferrable)
					fc_tgoidlist = lappend_oid(fc_tgoidlist, fc_pg_trigger->oid);
			}

			systable_endscan(fc_tgscan);
		}

		table_close(fc_tgrel, AccessShareLock);

		/*
		 * 现在我们可以为这个事务设置单个触发器的触发器状态。
		 */
		foreach(fc_lc, fc_tgoidlist)
		{
			Oid			fc_tgoid = lfirst_oid(fc_lc);
			SetConstraintState fc_state = afterTriggers.state;
			bool		fc_found = false;
			int			fc_i;

			for (fc_i = 0; fc_i < fc_state->numstates; fc_i++)
			{
				if (fc_state->trigstates[fc_i].sct_tgoid == fc_tgoid)
				{
					fc_state->trigstates[fc_i].sct_tgisdeferred = fc_stmt->deferred;
					fc_found = true;
					break;
				}
			}
			if (!fc_found)
			{
				afterTriggers.state =
					fc_SetConstraintStateAddItem(fc_state, fc_tgoid, fc_stmt->deferred);
			}
		}
	}

	/*
	 * SQL99要求在约束被设定为IMMEDIATE时，任何对该约束的延迟检查必须在执行SET CONSTRAINTS命令时进行——即SET CONSTRAINTS命令的效果是追溯生效的。我们已经更新了约束状态，因此扫描之前延迟事件的列表，以触发任何现在变为立即的事件。
	 *
	 * 显然，如果这是SET ... DEFERRED，则不能将任何未触发的事件转换为立即，因此在这种情况下我们无需做任何操作。
	 */
	if (!fc_stmt->deferred)
	{
		AfterTriggerEventList *fc_events = &afterTriggers.events;
		bool		fc_snapshot_set = false;

		while (fc_afterTriggerMarkEvents(fc_events, NULL, true))
		{
			CommandId	fc_firing_id = afterTriggers.firing_counter++;

			/*
			 * 确保已建立快照，以防触发函数需要它。请注意，如果我们没有找到至少一个必须立即触发的触发器，我们将避免设置快照。这是为了确保BEGIN; SET CONSTRAINTS ...; SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; ...正常工作。（如果我们处于事务开始阶段，则不可能有任何触发事件被排队。）
			 */
			if (!fc_snapshot_set)
			{
				PushActiveSnapshot(GetTransactionSnapshot());
				fc_snapshot_set = true;
			}

			/*
			 * 如果我们处于顶层事务级别，我们可以删除已触发的事件，但如果在子事务中就最好不这样做，因为子事务可能会被回滚。
			 */
			if (fc_afterTriggerInvokeEvents(fc_events, fc_firing_id, NULL,
										 !IsSubTransaction()))
				break;			/* 所有已触发 */
		}

		if (fc_snapshot_set)
			PopActiveSnapshot();
	}
}

/* ----------
 * AfterTriggerPendingOnRel()
 *		测试rel是否有任何待处理的后触发事件。
 *
 * 这被TRUNCATE、CLUSTER、ALTER TABLE等使用，以检测在关系上执行重大操作是否安全。请注意，仅检查本地待处理事件。我们假设对关系具有独占锁定保证其他后端中没有未服务事件——但拥有锁定并不防止我们自己的事件存在。
 *
 * 在某些场景下，移除待处理事件（更具体地说，通过当前子事务将其标记为DONE）是合理的，但在没有很多触发器语义知识的情况下，我们无法一般性地做到这一点。
 * ----------
 */
bool AfterTriggerPendingOnRel(Oid fc_relid)
{
	AfterTriggerEvent fc_event;
	AfterTriggerEventChunk *fc_chunk;
	int			fc_depth;

	/* 扫描排队事件 */
	for_each_event_chunk(fc_event, fc_chunk, afterTriggers.events)
	{
		AfterTriggerShared fc_evtshared = GetTriggerSharedData(fc_event);

		/*
		 * 我们可以忽略已完成的事件。（即便DONE标志被子事务回滚，这也是可以的，因为TRUNCATE或类似操作的效果也必须被回滚。）
		 */
		if (fc_event->ate_flags & AFTER_TRIGGER_DONE)
			continue;

		if (fc_evtshared->ats_relid == fc_relid)
			return true;
	}

	/*
	 * 还要扫描由于不完整查询而排队的事件。这只有在TRUNCATE等由同一关系上的更新查询中的函数或触发器执行时才重要，这相当奇怪，但我们还是检查一下。
	 */
	for (fc_depth = 0; fc_depth <= afterTriggers.query_depth && fc_depth < afterTriggers.maxquerydepth; fc_depth++)
	{
		for_each_event_chunk(fc_event, fc_chunk, afterTriggers.query_stack[fc_depth].events)
		{
			AfterTriggerShared fc_evtshared = GetTriggerSharedData(fc_event);

			if (fc_event->ate_flags & AFTER_TRIGGER_DONE)
				continue;

			if (fc_evtshared->ats_relid == fc_relid)
				return true;
		}
	}

	return false;
}

/* ----------
 * AfterTriggerSaveEvent()
 *
 *	由 ExecA[RS]...Triggers() 调用，以排队应该为事件触发的触发器。
 *
 *	注意：只要事件有任何关联的触发器（即使它们被禁用），此函数就会被调用。此函数决定实际上需要排队的触发器。即使对于该事件没有触发器，如果在使用过渡表的语句中有任何 AFTER STATEMENT 触发器，也会在每一行后调用此函数，以便可以构建过渡元组存储。此外，如果由于分区键的变化而将 UPDATE 的行移动到另一个分区，则在删除行时（以捕获旧行）和插入行到另一个分区时（以捕获新行）各调用一次此函数。这是单独完成的，因为 DELETE 和 INSERT 在不同的表上发生。
 *
 *	过渡元组存储现在被构建，而不是在事件从队列中拉出时，因为允许 AFTER ROW 触发器从语句的过渡表中选择。
 *
 *	此函数特别支持为正在经历跨分区更新的分区表排队更新事件，这可能有外键指向它。通常，分区表的行触发器不会被触发，因为在对分区表的操作后，修改的叶分区包含触发的相同触发器。但是，该一般方案在跨分区更新期间可能会导致外键触发器出现问题，该操作实现为对源分区的 DELETE 操作，然后对目标分区的 INSERT 操作。具体来说，触发 DELETE 触发器将导致考虑到原始命令为 UPDATE 时施加错误的外键操作；在这种情况下，此函数被调用时 relinfo 是分区表，src_partinfo 和 dst_partinfo 分别指向源和目标叶分区。
 *
 *	is_crosspart_update 在源分区上触发 DELETE 事件（将被忽略）或在根分区表上触发 UPDATE 事件时为真。
 * ----------
 */
static void fc_AfterTriggerSaveEvent(EState *fc_estate, ResultRelInfo *fc_relinfo,
					  ResultRelInfo *fc_src_partinfo,
					  ResultRelInfo *fc_dst_partinfo,
					  int fc_event, bool fc_row_trigger,
					  TupleTableSlot *fc_oldslot, TupleTableSlot *fc_newslot,
					  List *fc_recheckIndexes, Bitmapset *fc_modifiedCols,
					  TransitionCaptureState *fc_transition_capture,
					  bool fc_is_crosspart_update)
{
	Relation	fc_rel = fc_relinfo->ri_RelationDesc;
	TriggerDesc *fc_trigdesc = fc_relinfo->ri_TrigDesc;
	AfterTriggerEventData fc_new_event;
	AfterTriggerSharedData fc_new_shared;
	char		fc_relkind = fc_rel->rd_rel->relkind;
	int			fc_tgtype_event;
	int			fc_tgtype_level;
	int			fc_i;
	Tuplestorestate *fc_fdw_tuplestore = NULL;

	/*
	 * 检查状态。我们使用正常的测试而不是 Assert，因为考虑到错误配置的 RI 触发器，特别是延迟级联操作触发器，有可能在错误状态下到达这里。
	 */
	if (afterTriggers.query_depth < 0)
		elog(ERROR, "AfterTriggerSaveEvent() called outside of query");

	/* 确保我们有足够的空间在这个查询深度记录事件。 */
	if (afterTriggers.query_depth >= afterTriggers.maxquerydepth)
		fc_AfterTriggerEnlargeQueryState();

	/*
	 * 如果直接命名的关系有任何带有过渡表的触发器，那么我们需要捕获过渡元组。
	 */
	if (fc_row_trigger && fc_transition_capture != NULL)
	{
		TupleTableSlot *fc_original_insert_tuple = fc_transition_capture->tcs_original_insert_tuple;

		/*
		 * 根据事件在适当的过渡表中捕获旧元组。
		 */
		if (!TupIsNull(fc_oldslot))
		{
			Tuplestorestate *fc_old_tuplestore;

			fc_old_tuplestore = fc_GetAfterTriggersTransitionTable(fc_event,
															 fc_oldslot,
															 NULL,
															 fc_transition_capture);
			fc_TransitionTableAddTuple(fc_estate, fc_transition_capture, fc_relinfo,
									fc_oldslot, NULL, fc_old_tuplestore);
		}

		/*
		 * 根据事件在适当的过渡表中捕获新元组。
		 */
		if (!TupIsNull(fc_newslot))
		{
			Tuplestorestate *fc_new_tuplestore;

			fc_new_tuplestore = fc_GetAfterTriggersTransitionTable(fc_event,
															 NULL,
															 fc_newslot,
															 fc_transition_capture);
			fc_TransitionTableAddTuple(fc_estate, fc_transition_capture, fc_relinfo,
									fc_newslot, fc_original_insert_tuple, fc_new_tuplestore);
		}

		/*
		 * 如果过渡表是我们在这里的唯一原因，返回。如上所述，
		 * 在存在过渡表的情况下，我们也可能在更新元组路由期间在这里，
		 * 在这种情况下，分别为OLD和NEW调用此函数，因此我们期望
		 * 其中恰好一个为NULL。
		 */
		if (fc_trigdesc == NULL ||
			(fc_event == TRIGGER_EVENT_DELETE && !fc_trigdesc->trig_delete_after_row) ||
			(fc_event == TRIGGER_EVENT_INSERT && !fc_trigdesc->trig_insert_after_row) ||
			(fc_event == TRIGGER_EVENT_UPDATE && !fc_trigdesc->trig_update_after_row) ||
			(fc_event == TRIGGER_EVENT_UPDATE && (TupIsNull(fc_oldslot) ^ TupIsNull(fc_newslot))))
			return;
	}

	/*
	 * 除了跨分区更新的特殊情况，我们通常不会在这里看到分区表
	 * 进行行级触发器。在这种情况下，nodeModifyTable.c:ExecCrossPartitionUpdateForeignKey()
	 * 在这里调用以在根目标分区表上排队更新事件，同时
	 * 传递源和目标分区及其元组。
	 */
	Assert(!fc_row_trigger ||
		   fc_rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE ||
		   (fc_is_crosspart_update &&
			TRIGGER_FIRED_BY_UPDATE(fc_event) &&
			fc_src_partinfo != NULL && fc_dst_partinfo != NULL));

	/*
	 * 验证事件代码并收集相关元组CTID。
	 *
	 * 事件代码将作为位掩码和数组偏移量使用，因此
	 * 验证非常重要，以确保我们不会越界访问我们的
	 * 数组。
	 *
	 * 此外，如果我们考虑语句级触发器，请检查我们是否
	 * 已经为此事件排队了一组触发器，如果是，则取消先前的一组。
	 * 这确保了语句级触发器在每个语句中只触发一次，并且
	 * 随后触发行级触发器。
	 */
	switch (fc_event)
	{
		case TRIGGER_EVENT_INSERT:
			fc_tgtype_event = TRIGGER_TYPE_INSERT;
			if (fc_row_trigger)
			{
				Assert(fc_oldslot == NULL);
				Assert(fc_newslot != NULL);
				ItemPointerCopy(&(fc_newslot->tts_tid), &(fc_new_event.ate_ctid1));
				ItemPointerSetInvalid(&(fc_new_event.ate_ctid2));
			}
			else
			{
				Assert(fc_oldslot == NULL);
				Assert(fc_newslot == NULL);
				ItemPointerSetInvalid(&(fc_new_event.ate_ctid1));
				ItemPointerSetInvalid(&(fc_new_event.ate_ctid2));
				fc_cancel_prior_stmt_triggers(RelationGetRelid(fc_rel),
										   CMD_INSERT, fc_event);
			}
			break;
		case TRIGGER_EVENT_DELETE:
			fc_tgtype_event = TRIGGER_TYPE_DELETE;
			if (fc_row_trigger)
			{
				Assert(fc_oldslot != NULL);
				Assert(fc_newslot == NULL);
				ItemPointerCopy(&(fc_oldslot->tts_tid), &(fc_new_event.ate_ctid1));
				ItemPointerSetInvalid(&(fc_new_event.ate_ctid2));
			}
			else
			{
				Assert(fc_oldslot == NULL);
				Assert(fc_newslot == NULL);
				ItemPointerSetInvalid(&(fc_new_event.ate_ctid1));
				ItemPointerSetInvalid(&(fc_new_event.ate_ctid2));
				fc_cancel_prior_stmt_triggers(RelationGetRelid(fc_rel),
										   CMD_DELETE, fc_event);
			}
			break;
		case TRIGGER_EVENT_UPDATE:
			fc_tgtype_event = TRIGGER_TYPE_UPDATE;
			if (fc_row_trigger)
			{
				Assert(fc_oldslot != NULL);
				Assert(fc_newslot != NULL);
				ItemPointerCopy(&(fc_oldslot->tts_tid), &(fc_new_event.ate_ctid1));
				ItemPointerCopy(&(fc_newslot->tts_tid), &(fc_new_event.ate_ctid2));

				/*
				 * 还要记住获取这些元组的分区OID，
				 * 以便在AfterTriggerExecute()中稍后提取。
				 */
				if (fc_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
				{
					Assert(fc_src_partinfo != NULL && fc_dst_partinfo != NULL);
					fc_new_event.ate_src_part =
						RelationGetRelid(fc_src_partinfo->ri_RelationDesc);
					fc_new_event.ate_dst_part =
						RelationGetRelid(fc_dst_partinfo->ri_RelationDesc);
				}
			}
			else
			{
				Assert(fc_oldslot == NULL);
				Assert(fc_newslot == NULL);
				ItemPointerSetInvalid(&(fc_new_event.ate_ctid1));
				ItemPointerSetInvalid(&(fc_new_event.ate_ctid2));
				fc_cancel_prior_stmt_triggers(RelationGetRelid(fc_rel),
										   CMD_UPDATE, fc_event);
			}
			break;
		case TRIGGER_EVENT_TRUNCATE:
			fc_tgtype_event = TRIGGER_TYPE_TRUNCATE;
			Assert(fc_oldslot == NULL);
			Assert(fc_newslot == NULL);
			ItemPointerSetInvalid(&(fc_new_event.ate_ctid1));
			ItemPointerSetInvalid(&(fc_new_event.ate_ctid2));
			break;
		default:
			elog(ERROR, "invalid after-trigger event code: %d", fc_event);
			fc_tgtype_event = 0;	/* 保持编译器安静 */
			break;
	}

	/* 确定标志 */
	if (!(fc_relkind == RELKIND_FOREIGN_TABLE && fc_row_trigger))
	{
		if (fc_row_trigger && fc_event == TRIGGER_EVENT_UPDATE)
		{
			if (fc_relkind == RELKIND_PARTITIONED_TABLE)
				fc_new_event.ate_flags = AFTER_TRIGGER_CP_UPDATE;
			else
				fc_new_event.ate_flags = AFTER_TRIGGER_2CTID;
		}
		else
			fc_new_event.ate_flags = AFTER_TRIGGER_1CTID;
	}

	/* 否则，我们将为每个触发器初始化ate_flags */

	fc_tgtype_level = (fc_row_trigger ? TRIGGER_TYPE_ROW : TRIGGER_TYPE_STATEMENT);

	/*
	 * 必须将源和目标分区元组转换/复制到
	 * 根分区表的格式/槽中，因为下面循环中的处理
	 * 期望oldslot和newslot元组都采用这种形式。
	 */
	if (fc_row_trigger && fc_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
	{
		TupleTableSlot *fc_rootslot;
		TupleConversionMap *fc_map;

		fc_rootslot = ExecGetTriggerOldSlot(fc_estate, fc_relinfo);
		fc_map = ExecGetChildToRootMap(fc_src_partinfo);
		if (fc_map)
			fc_oldslot = execute_attr_map_slot(fc_map->attrMap,
											fc_oldslot,
											fc_rootslot);
		else
			fc_oldslot = ExecCopySlot(fc_rootslot, fc_oldslot);

		fc_rootslot = ExecGetTriggerNewSlot(fc_estate, fc_relinfo);
		fc_map = ExecGetChildToRootMap(fc_dst_partinfo);
		if (fc_map)
			fc_newslot = execute_attr_map_slot(fc_map->attrMap,
											fc_newslot,
											fc_rootslot);
		else
			fc_newslot = ExecCopySlot(fc_rootslot, fc_newslot);
	}

	for (fc_i = 0; fc_i < fc_trigdesc->numtriggers; fc_i++)
	{
		Trigger    *fc_trigger = &fc_trigdesc->triggers[fc_i];

		if (!TRIGGER_TYPE_MATCHES(fc_trigger->tgtype,
								  fc_tgtype_level,
								  TRIGGER_TYPE_AFTER,
								  fc_tgtype_event))
			continue;
		if (!fc_TriggerEnabled(fc_estate, fc_relinfo, fc_trigger, fc_event,
							fc_modifiedCols, fc_oldslot, fc_newslot))
			continue;

		if (fc_relkind == RELKIND_FOREIGN_TABLE && fc_row_trigger)
		{
			if (fc_fdw_tuplestore == NULL)
			{
				fc_fdw_tuplestore = fc_GetCurrentFDWTuplestore();
				fc_new_event.ate_flags = AFTER_TRIGGER_FDW_FETCH;
			}
			else
				/* 同一元组的后续事件 */
				fc_new_event.ate_flags = AFTER_TRIGGER_FDW_REUSE;
		}

		/*
		 * 如果触发器是外键约束触发器，则在某些情况下我们可以
		 * 跳过排队事件，因为我们可以通过检查确认FK约束仍会通过。
		 * 在分区表的跨分区更新过程中，也有一些情况下可以跳过
		 * 排队事件。
		 */
		if (TRIGGER_FIRED_BY_UPDATE(fc_event) || TRIGGER_FIRED_BY_DELETE(fc_event))
		{
			switch (RI_FKey_trigger_type(fc_trigger->tgfoid))
			{
				case RI_TRIGGER_PK:

					/*
					 * 对于分区主键表的跨分区更新，
					 * 除非约束起源于分区本身（!tgisclone），
					 * 否则跳过源叶分区上组件删除触发的事件，
					 * 因为将会在根（分区）目标表上触发的更新事件
					 * 将用于执行必要的外键约束操作。
					 */
					if (fc_is_crosspart_update &&
						TRIGGER_FIRED_BY_DELETE(fc_event) &&
						fc_trigger->tgisclone)
						continue;

					/* 更新或删除触发器的主键表 */
					if (!RI_FKey_pk_upd_check_required(fc_trigger, fc_rel,
													   fc_oldslot, fc_newslot))
					{
						/* 跳过排队此事件 */
						continue;
					}
					break;

				case RI_TRIGGER_FK:

					/*
					 * 更新触发器的外键表。在进行跨分区更新时，我们可以跳过在分区表上触发的更新事件，因为目标叶子分区上触发的插入事件就足以进行必要的外键检查。此外，RI_FKey_fk_upd_check_required() 期望传入一个包含系统属性的元组，而这些属性大多数在属于分区表的虚拟槽中是不存在的。
					 */
					if (fc_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
						!RI_FKey_fk_upd_check_required(fc_trigger, fc_rel,
													   fc_oldslot, fc_newslot))
					{
						/* 跳过排队此事件 */
						continue;
					}
					break;

				case RI_TRIGGER_NONE:

					/*
					 * 不是外键触发器。在分区表的跨分区更新中，不需要排队在更新期间触发的更新事件，因为在此更新中受影响的叶子分区必须存在相同的行触发器，且在它们上触发的事件会被排队处理。
					 */
					if (fc_row_trigger &&
						fc_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
						continue;
					break;
			}
		}

		/*
		 * 如果触发器是延迟唯一性约束检查触发器，仅在唯一性约束可能被违反的情况下进行排队，这点我们可以从索引插入时得知。
		 */
		if (fc_trigger->tgfoid == F_UNIQUE_KEY_RECHECK)
		{
			if (!list_member_oid(fc_recheckIndexes, fc_trigger->tgconstrindid))
				continue;		/* 唯一性绝对没有被违反 */
		}

		/*
		 * 填充事件结构并将其添加到当前查询的队列中。请注意，当此触发器不使用过渡表时，我们将 ats_table 设置为 NULL，以提高共享事件数据的可共享性。
		 */
		fc_new_shared.ats_event =
			(fc_event & TRIGGER_EVENT_OPMASK) |
			(fc_row_trigger ? TRIGGER_EVENT_ROW : 0) |
			(fc_trigger->tgdeferrable ? AFTER_TRIGGER_DEFERRABLE : 0) |
			(fc_trigger->tginitdeferred ? AFTER_TRIGGER_INITDEFERRED : 0);
		fc_new_shared.ats_tgoid = fc_trigger->tgoid;
		fc_new_shared.ats_relid = RelationGetRelid(fc_rel);
		fc_new_shared.ats_firing_id = 0;
		if ((fc_trigger->tgoldtable || fc_trigger->tgnewtable) &&
			fc_transition_capture != NULL)
			fc_new_shared.ats_table = fc_transition_capture->tcs_private;
		else
			fc_new_shared.ats_table = NULL;
		fc_new_shared.ats_modifiedcols = fc_modifiedCols;

		fc_afterTriggerAddEvent(&afterTriggers.query_stack[afterTriggers.query_depth].events,
							 &fc_new_event, &fc_new_shared);
	}

	/*
	 * 最后，暂存任何外部元组。元组存储将它们压缩为最小的元组，因此这会丢失任何系统列。执行器在我们之前已丢失这些列，基于无关原因，所以这没问题。
	 */
	if (fc_fdw_tuplestore)
	{
		if (fc_oldslot != NULL)
			tuplestore_puttupleslot(fc_fdw_tuplestore, fc_oldslot);
		if (fc_newslot != NULL)
			tuplestore_puttupleslot(fc_fdw_tuplestore, fc_newslot);
	}
}

/*
 * 检测我们是否已经为给定的关系 + 操作排队了 BEFORE STATEMENT 触发器，并设置标志以便下一次调用会报告“true”。
 */
static bool fc_before_stmt_triggers_fired(Oid fc_relid, CmdType fc_cmdType)
{
	bool		fc_result;
	AfterTriggersTableData *fc_table;

	/* 检查状态，如 AfterTriggerSaveEvent。 */
	if (afterTriggers.query_depth < 0)
		elog(ERROR, "before_stmt_triggers_fired() called outside of query");

	/* 确保我们有足够的空间在这个查询深度记录事件。 */
	if (afterTriggers.query_depth >= afterTriggers.maxquerydepth)
		fc_AfterTriggerEnlargeQueryState();

	/*
	 * 我们在 AfterTriggersTableData 中保持此状态，该结构还持有关系 + 操作的过渡表。通过这种方式，如果我们被迫创建一组新的过渡表，因为在我们已经触发触发器后又有更多元组被输入，我们将允许排队新一组语句触发器。
	 */
	fc_table = fc_GetAfterTriggersTableData(fc_relid, fc_cmdType);
	fc_result = fc_table->before_trig_done;
	fc_table->before_trig_done = true;
	return fc_result;
}

/*
 * 如果我们之前为给定的关系 + 操作排队了一组 AFTER STATEMENT 触发器，并且它们尚未触发，则取消它们。调用者将排队一组新的触发器，这组触发器是在当前子语句可能排队的任何行级触发器之后，以尽可能保持 AFTER ROW 触发器在 AFTER STATEMENT 触发器之前触发，并且后者仅触发一次。这解决了多个 FK 约束触发器顺序排队同一表的触发器到同一个触发器查询级别的情况。我们无法完全防止奇怪的行为：如果有 AFTER ROW 触发器使用过渡表，我们不希望在第一个此类触发器看到它们之后更改过渡表。在这种情况下，任何额外的事件将导致创建新的过渡表，并允许新的语句触发器触发。
 *
 * 这还保存了当前事件列表的位置，以便稍后调用此函数时可以快速找到我们即将排队并取消的触发器。
 */
static void fc_cancel_prior_stmt_triggers(Oid fc_relid, CmdType fc_cmdType, int fc_tgevent)
{
	AfterTriggersTableData *fc_table;
	AfterTriggersQueryData *fc_qs = &afterTriggers.query_stack[afterTriggers.query_depth];

	/*
	 * 我们将此状态保存在 AfterTriggersTableData 中，该数据还持有
	 * 关系 + 操作的过渡表。通过这种方式，如果因为在我们已经触发触发器后又插入了更多元组而被迫创建一组新的过渡表，我们将允许新的语句触发器排队，而不取消旧的触发器。
	 */
	fc_table = fc_GetAfterTriggersTableData(fc_relid, fc_cmdType);

	if (fc_table->after_trig_done)
	{
		/*
		 * 我们希望从我们插入任何语句触发器之前存在的尾部位置开始扫描。但是事件列表可能在那时完全为空，在这种情况下，从当前头部开始扫描。
		 */
		AfterTriggerEvent fc_event;
		AfterTriggerEventChunk *fc_chunk;

		if (fc_table->after_trig_events.tail)
		{
			fc_chunk = fc_table->after_trig_events.tail;
			fc_event = (AfterTriggerEvent) fc_table->after_trig_events.tailfree;
		}
		else
		{
			fc_chunk = fc_qs->events.head;
			fc_event = NULL;
		}

		for_each_chunk_from(fc_chunk)
		{
			if (fc_event == NULL)
				fc_event = (AfterTriggerEvent) CHUNK_DATA_START(fc_chunk);
			for_each_event_from(fc_event, fc_chunk)
			{
				AfterTriggerShared fc_evtshared = GetTriggerSharedData(fc_event);

				/*
				 * 当我们到达不属于目标关系的 AS 触发器的事件时退出循环。
				 */
				if (fc_evtshared->ats_relid != fc_relid)
					goto done;
				if ((fc_evtshared->ats_event & TRIGGER_EVENT_OPMASK) != fc_tgevent)
					goto done;
				if (!TRIGGER_FIRED_FOR_STATEMENT(fc_evtshared->ats_event))
					goto done;
				if (!TRIGGER_FIRED_AFTER(fc_evtshared->ats_event))
					goto done;
				/* 好的，标记为完成 */
				fc_event->ate_flags &= ~AFTER_TRIGGER_IN_PROGRESS;
				fc_event->ate_flags |= AFTER_TRIGGER_DONE;
			}
			/* 信号我们必须为下一块重新初始化事件指针 */
			fc_event = NULL;
		}
	}
done:

	/* 无论如何，保存当前插入点以备下次使用 */
	fc_table->after_trig_done = true;
	fc_table->after_trig_events = fc_qs->events;
}

/*
 * SQL 函数 pg_trigger_depth()
 */
Datum pg_trigger_depth(PG_FUNCTION_ARGS)
{
	PG_RETURN_INT32(MyTriggerDepth);
}
